diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/AbstractProject.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/AbstractProject.cs index 680dd661ce847cd568e1d4704f6e2a4c581d1ada..e31b7dc639f34849365cfa9ba2c4dfc209fff9b1 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/AbstractProject.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/AbstractProject.cs @@ -557,6 +557,8 @@ protected bool CanConvertToProjectReferences protected int AddMetadataReferenceAndTryConvertingToProjectReferenceIfPossible(string filePath, MetadataReferenceProperties properties) { + AssertIsForeground(); + // If this file is coming from a project, then we should convert it to a project reference instead if (this.CanConvertToProjectReferences && ProjectTracker.TryGetProjectByBinPath(filePath, out var project)) { @@ -618,6 +620,8 @@ protected int AddMetadataReferenceAndTryConvertingToProjectReferenceIfPossible(s protected void RemoveMetadataReference(string filePath) { + AssertIsForeground(); + // Is this a reference we converted to a project reference? if (TryGetMetadataFileNameToConvertedProjectReference(filePath, out var projectReference)) { @@ -723,6 +727,8 @@ private void OnImportChangedAfterDelay(Task previous, object state) private void OnAnalyzerChanged(object sender, EventArgs e) { + AssertIsForeground(); + // Postpone handler's actions to prevent deadlock. This AnalyzeChanged event can // be invoked while the FileChangeService lock is held, and VisualStudioAnalyzer's // efforts to listen to file changes can lead to a deadlock situation. @@ -740,6 +746,8 @@ private void OnAnalyzerChanged(object sender, EventArgs e) // Internal for unit testing internal void AddProjectReference(ProjectReference projectReference) { + AssertIsForeground(); + // dev11 is sometimes calling us multiple times for the same data if (!CanAddProjectReference(projectReference)) { @@ -824,6 +832,8 @@ private bool TransitivelyReferencesWorker(ProjectId projectId, HashSet host.OnDocumentOpened(document.Id, document.GetOpenTextBuffer(), isCurrentContext)); @@ -856,6 +868,8 @@ private static void OnDocumentClosing(object sender, bool updateActiveContext) AbstractProject project = (AbstractProject)document.Project; var projectTracker = project.ProjectTracker; + project.AssertIsForeground(); + if (project._pushingChangesToWorkspaceHosts) { projectTracker.NotifyWorkspaceHosts(host => host.OnDocumentClosed(document.Id, document.GetOpenTextBuffer(), document.Loader, updateActiveContext)); @@ -867,6 +881,8 @@ private static void OnDocumentUpdatedOnDisk(object sender, EventArgs e) IVisualStudioHostDocument document = (IVisualStudioHostDocument)sender; AbstractProject project = (AbstractProject)document.Project; + project.AssertIsForeground(); + if (project._pushingChangesToWorkspaceHosts) { project.ProjectTracker.NotifyWorkspaceHosts(host => host.OnDocumentTextUpdatedOnDisk(document.Id)); @@ -878,6 +894,8 @@ private static void OnAdditionalDocumentOpened(object sender, bool isCurrentCont IVisualStudioHostDocument document = (IVisualStudioHostDocument)sender; AbstractProject project = (AbstractProject)document.Project; + project.AssertIsForeground(); + if (project._pushingChangesToWorkspaceHosts) { project.ProjectTracker.NotifyWorkspaceHosts(host => host.OnAdditionalDocumentOpened(document.Id, document.GetOpenTextBuffer(), isCurrentContext)); @@ -894,6 +912,8 @@ private static void OnAdditionalDocumentClosing(object sender, bool notUsed) AbstractProject project = (AbstractProject)document.Project; var projectTracker = project.ProjectTracker; + project.AssertIsForeground(); + if (project._pushingChangesToWorkspaceHosts) { projectTracker.NotifyWorkspaceHosts(host => host.OnAdditionalDocumentClosed(document.Id, document.GetOpenTextBuffer(), document.Loader)); @@ -905,6 +925,8 @@ private static void OnAdditionalDocumentUpdatedOnDisk(object sender, EventArgs e IVisualStudioHostDocument document = (IVisualStudioHostDocument)sender; AbstractProject project = (AbstractProject)document.Project; + project.AssertIsForeground(); + if (project._pushingChangesToWorkspaceHosts) { project.ProjectTracker.NotifyWorkspaceHosts(host => host.OnAdditionalDocumentTextUpdatedOnDisk(document.Id)); @@ -917,6 +939,8 @@ private static void OnAdditionalDocumentUpdatedOnDisk(object sender, EventArgs e Func getIsCurrentContext, Func> getFolderNames) { + AssertIsForeground(); + // We can currently be on a background thread. // So, hookup the handlers when creating the standard text document, as we might receive these handler notifications on the UI thread. var document = this.DocumentProvider.TryGetDocumentForFile( @@ -957,6 +981,8 @@ protected void AddUntrackedFile(string filename) protected void RemoveFile(string filename) { + AssertIsForeground(); + lock (_gate) { // Remove this as an untracked file, if it is @@ -977,6 +1003,8 @@ protected void RemoveFile(string filename) internal void AddDocument(IVisualStudioHostDocument document, bool isCurrentContext, bool hookupHandlers) { + AssertIsForeground(); + // We do not want to allow message pumping/reentrancy when processing project system changes. using (Dispatcher.CurrentDispatcher.DisableProcessing()) { @@ -1014,6 +1042,8 @@ internal void AddDocument(IVisualStudioHostDocument document, bool isCurrentCont internal void RemoveDocument(IVisualStudioHostDocument document) { + AssertIsForeground(); + // We do not want to allow message pumping/reentrancy when processing project system changes. using (Dispatcher.CurrentDispatcher.DisableProcessing()) { @@ -1030,6 +1060,8 @@ internal void RemoveDocument(IVisualStudioHostDocument document) internal void AddAdditionalDocument(IVisualStudioHostDocument document, bool isCurrentContext) { + AssertIsForeground(); + lock (_gate) { _additionalDocuments.Add(document.Id, document); @@ -1056,6 +1088,8 @@ internal void AddAdditionalDocument(IVisualStudioHostDocument document, bool isC internal void RemoveAdditionalDocument(IVisualStudioHostDocument document) { + AssertIsForeground(); + lock (_gate) { _additionalDocuments.Remove(document.Id); @@ -1131,6 +1165,8 @@ public virtual void Disconnect() internal void TryProjectConversionForIntroducedOutputPath(string binPath, AbstractProject projectToReference) { + AssertIsForeground(); + if (this.CanConvertToProjectReferences) { // We should not already have references for this, since we're only introducing the path for the first time @@ -1157,6 +1193,8 @@ internal void TryProjectConversionForIntroducedOutputPath(string binPath, Abstra internal void UndoProjectReferenceConversionForDisappearingOutputPath(string binPath) { + AssertIsForeground(); + if (TryGetMetadataFileNameToConvertedProjectReference(binPath, out var projectReference)) { // We converted this, so convert it back to a metadata reference @@ -1175,6 +1213,8 @@ internal void UndoProjectReferenceConversionForDisappearingOutputPath(string bin protected void UpdateMetadataReferenceAliases(string file, ImmutableArray aliases) { + AssertIsForeground(); + file = FileUtilities.NormalizeAbsolutePath(file); // Have we converted these to project references? @@ -1198,6 +1238,8 @@ protected void UpdateMetadataReferenceAliases(string file, ImmutableArray aliases) { + AssertIsForeground(); + var projectReference = GetCurrentProjectReferences().Single(r => r.ProjectId == referencedProject.Id); var newProjectReference = new ProjectReference(referencedProject.Id, aliases, projectReference.EmbedInteropTypes); @@ -1217,6 +1259,8 @@ protected void UpdateProjectReferenceAliases(AbstractProject referencedProject, private void UninitializeDocument(IVisualStudioHostDocument document) { + AssertIsForeground(); + if (_pushingChangesToWorkspaceHosts) { if (document.IsOpen) @@ -1236,6 +1280,8 @@ private void UninitializeDocument(IVisualStudioHostDocument document) private void UninitializeAdditionalDocument(IVisualStudioHostDocument document) { + AssertIsForeground(); + if (_pushingChangesToWorkspaceHosts) { if (document.IsOpen) @@ -1269,6 +1315,7 @@ internal void StopPushingToWorkspaceHosts() internal void StartPushingToWorkspaceAndNotifyOfOpenDocuments() { + AssertIsForeground(); StartPushingToWorkspaceAndNotifyOfOpenDocuments(this); } @@ -1282,6 +1329,8 @@ internal bool PushingChangesToWorkspaceHosts protected void UpdateRuleSetError(IRuleSetFile ruleSetFile) { + AssertIsForeground(); + if (this.HostDiagnosticUpdateSource == null) { return; @@ -1304,6 +1353,8 @@ protected void UpdateRuleSetError(IRuleSetFile ruleSetFile) protected void SetObjOutputPathAndRelatedData(string objOutputPath) { + AssertIsForeground(); + var currentObjOutputPath = this.ObjOutputPath; if (PathUtilities.IsAbsolute(objOutputPath) && !string.Equals(currentObjOutputPath, objOutputPath, StringComparison.OrdinalIgnoreCase)) { @@ -1332,6 +1383,8 @@ protected void SetObjOutputPathAndRelatedData(string objOutputPath) private void UpdateAssemblyName() { + AssertIsForeground(); + // set assembly name if changed // we use designTimeOutputPath to get assembly name since it is more reliable way to get the assembly name. // otherwise, friend assembly all get messed up. @@ -1349,6 +1402,8 @@ private void UpdateAssemblyName() protected void SetBinOutputPathAndRelatedData(string binOutputPath) { + AssertIsForeground(); + // refresh final output path var currentBinOutputPath = this.BinOutputPath; if (binOutputPath != null && !string.Equals(currentBinOutputPath, binOutputPath, StringComparison.OrdinalIgnoreCase)) @@ -1375,6 +1430,8 @@ protected void UpdateProjectFilePath(string newFilePath) protected void UpdateProjectDisplayNameAndFilePath(string newDisplayName, string newFilePath) { + AssertIsForeground(); + bool updateMade = false; if (newDisplayName != null && this.DisplayName != newDisplayName) @@ -1398,6 +1455,8 @@ protected void UpdateProjectDisplayNameAndFilePath(string newDisplayName, string private static void StartPushingToWorkspaceAndNotifyOfOpenDocuments(AbstractProject project) { + project.AssertIsForeground(); + // If a document is opened in a project but we haven't started pushing yet, we want to stop doing lazy // loading for this project and get it up to date so the user gets a fast experience there. If the file // was presented as open to us right away, then we'll never do this in OnDocumentOpened, so we should do @@ -1462,6 +1521,8 @@ protected static bool FilterException(Exception e) public IReadOnlyList GetFolderNamesFromHierarchy(uint documentItemID) { + AssertIsForeground(); + if (documentItemID != (uint)VSConstants.VSITEMID.Nil && Hierarchy.GetProperty(documentItemID, (int)VsHierarchyPropID.Parent, out var parentObj) == VSConstants.S_OK) { var parentID = UnboxVSItemId(parentObj); @@ -1476,6 +1537,8 @@ public IReadOnlyList GetFolderNamesFromHierarchy(uint documentItemID) private IReadOnlyList GetFolderNamesForFolder(uint folderItemID) { + AssertIsForeground(); + // note: use of tmpFolders is assuming this API is called on UI thread only. _tmpFolders.Clear(); if (!_folderNameMap.TryGetValue(folderItemID, out var names)) diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/AbstractProject_Analyzers.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/AbstractProject_Analyzers.cs index 55b739a3e0f0909f1ba3b46cdbf79546ca8cbc04..47de2ead0aad95c2e3f74da4266c054511686e92 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/AbstractProject_Analyzers.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/AbstractProject_Analyzers.cs @@ -21,6 +21,8 @@ internal partial class AbstractProject public void AddAnalyzerReference(string analyzerAssemblyFullPath) { + AssertIsForeground(); + if (CurrentProjectAnalyzersContains(analyzerAssemblyFullPath)) { return; @@ -70,6 +72,8 @@ public void AddAnalyzerReference(string analyzerAssemblyFullPath) public void RemoveAnalyzerReference(string analyzerAssemblyFullPath) { + AssertIsForeground(); + if (!TryGetAnalyzer(analyzerAssemblyFullPath, out var analyzer)) { return; @@ -100,6 +104,8 @@ public void RemoveAnalyzerReference(string analyzerAssemblyFullPath) public void SetRuleSetFile(string ruleSetFileFullPath) { + AssertIsForeground(); + if (ruleSetFileFullPath == null) { ruleSetFileFullPath = string.Empty; @@ -123,6 +129,8 @@ public void SetRuleSetFile(string ruleSetFileFullPath) public void AddAdditionalFile(string additionalFilePath, Func getIsInCurrentContext) { + AssertIsForeground(); + var document = this.DocumentProvider.TryGetDocumentForFile( this, filePath: additionalFilePath, @@ -180,6 +188,8 @@ private void ClearAnalyzerRuleSet() // internal for testing purpose. internal void OnRuleSetFileUpdateOnDisk(object sender, EventArgs e) { + AssertIsForeground(); + var filePath = this.RuleSetFile.FilePath; ResetAnalyzerRuleSet(filePath); diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/AbstractProject_Options.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/AbstractProject_Options.cs index dc7c35317e1a67143061f9ff3703c377126f2ec6..74fd879a249377e7dc615496e0ba19b937604b62 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/AbstractProject_Options.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/AbstractProject_Options.cs @@ -51,6 +51,8 @@ private void SetArgumentsCore(string commandLine, CommandLineArguments commandLi /// protected void UpdateOptions() { + AssertIsForeground(); + CommandLineArguments lastParsedCommandLineArguments = _lastParsedCommandLineArguments; Contract.ThrowIfNull(lastParsedCommandLineArguments); @@ -70,6 +72,8 @@ protected void UpdateOptions() /// protected CommandLineArguments SetArgumentsAndUpdateOptions(string commandLine) { + AssertIsForeground(); + var commandLineArguments = SetArguments(commandLine); UpdateOptions(); return commandLineArguments; @@ -109,6 +113,8 @@ protected CommandLineArguments SetArguments(string commandLine) /// protected void SetOptions(CompilationOptions newCompilationOptions, ParseOptions newParseOptions) { + AssertIsForeground(); + this.UpdateRuleSetError(this.RuleSetFile); // Set options. diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker.cs index b20c5ccb5f2ad58bc6c39a4d1040d3a2ba503253..bfb83f955c82c789df409f75d86e6fe966f7c6cf 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker.cs @@ -65,16 +65,9 @@ internal sealed partial class VisualStudioProjectTracker : ForegroundThreadAffin /// private readonly Dictionary> _projectsByBinPath = new Dictionary>(StringComparer.OrdinalIgnoreCase); - /// - /// Holds the task with continuations to sequentially execute all the foreground affinitized actions on the foreground task scheduler. - /// More specifically, all the notifications to workspace hosts are executed on the foreground thread. However, the project system might make project state changes - /// and request notifications to workspace hosts on background thread. So we queue up all the notifications for project state changes onto this task and execute them on the foreground thread. - /// - private Task _taskForForegroundAffinitizedActions = Task.CompletedTask; - private readonly Dictionary _projectMap; private readonly Dictionary _projectPathToIdMap; - #endregion +#endregion /// /// Provided to not break CodeLens which has a dependency on this API until there is a @@ -133,35 +126,6 @@ public VisualStudioProjectTracker(IServiceProvider serviceProvider, HostWorkspac } } - private void ScheduleForegroundAffinitizedAction(Action action) - { - AssertIsBackground(); - - lock (_gate) - { - _taskForForegroundAffinitizedActions = _taskForForegroundAffinitizedActions.SafeContinueWith(_ => action(), ForegroundTaskScheduler); - } - } - - /// - /// If invoked on the foreground thread, the action is executed right away. - /// Otherwise, the action is scheduled on foreground task scheduler. - /// - /// Action that needs to be executed on a foreground thread. - private void ExecuteOrScheduleForegroundAffinitizedAction(Action action) - { - if (IsForeground()) - { - // We are already on the foreground thread, execute the given action. - action(); - } - else - { - // Schedule the update on the foreground task scheduler. - ScheduleForegroundAffinitizedAction(action); - } - } - public void RegisterSolutionProperties(SolutionId solutionId) { AssertIsForeground(); @@ -204,12 +168,6 @@ public string GetWorkingFolderPath(Solution solution) } public void RegisterWorkspaceHost(IVisualStudioWorkspaceHost host) - { - ExecuteOrScheduleForegroundAffinitizedAction( - () => RegisterWorkspaceHostOnForeground(host)); - } - - private void RegisterWorkspaceHostOnForeground(IVisualStudioWorkspaceHost host) { this.AssertIsForeground(); @@ -298,16 +256,9 @@ internal bool ContainsProject(AbstractProject project) /// /// Add a project to the workspace. - /// If invoked on the foreground thread, the add is executed right away. - /// Otherwise, the add is scheduled on foreground task scheduler. /// - /// This method may be called on a background thread. + /// This method must be called on the foreground thread. internal void AddProject(AbstractProject project) - { - ExecuteOrScheduleForegroundAffinitizedAction(() => AddProject_Foreground(project)); - } - - private void AddProject_Foreground(AbstractProject project) { AssertIsForeground(); @@ -317,11 +268,11 @@ private void AddProject_Foreground(AbstractProject project) } // UpdateProjectBinPath is defensively executed on the foreground thread as it calls back into referencing projects to perform metadata to P2P reference conversions. - UpdateProjectBinPath_Foreground(project, null, project.BinOutputPath); + UpdateProjectBinPath(project, null, project.BinOutputPath); if (_solutionLoadComplete) { - StartPushingToWorkspaceAndNotifyOfOpenDocuments_Foreground(SpecializedCollections.SingletonEnumerable(project)); + StartPushingToWorkspaceAndNotifyOfOpenDocuments(SpecializedCollections.SingletonEnumerable(project)); } else { @@ -331,25 +282,12 @@ private void AddProject_Foreground(AbstractProject project) /// /// Starts pushing events from the given projects to the workspace hosts and notifies about open documents. - /// If invoked on the foreground thread, it is executed right away. - /// Otherwise, it is scheduled on foreground task scheduler. /// - /// This method may be called on a background thread. + /// This method must be called on the foreground thread. internal void StartPushingToWorkspaceAndNotifyOfOpenDocuments(IEnumerable projects) - { - ExecuteOrScheduleForegroundAffinitizedAction(() => StartPushingToWorkspaceAndNotifyOfOpenDocuments_Foreground(projects)); - } - - private void StartPushingToWorkspaceAndNotifyOfOpenDocuments_Foreground(IEnumerable projects) { AssertIsForeground(); - // StartPushingToWorkspaceAndNotifyOfOpenDocuments might be invoked from a background thread, - // and hence StartPushingToWorkspaceAndNotifyOfOpenDocuments_Foreground scheduled to be executed later on the foreground task scheduler. - // By the time it gets scheduled, we might have removed some project(s) from the tracker on the UI thread. - // So, we filter out the projects that have been removed from the tracker. - projects = projects.Where(p => this.ContainsProject(p)); - using (Dispatcher.CurrentDispatcher.DisableProcessing()) { foreach (var hostState in _workspaceHosts) @@ -363,14 +301,6 @@ private void StartPushingToWorkspaceAndNotifyOfOpenDocuments_Foreground(IEnumera /// Remove a project from the workspace. /// internal void RemoveProject(AbstractProject project) - { - ExecuteOrScheduleForegroundAffinitizedAction(() => RemoveProject_Foreground(project)); - } - - /// - /// Remove a project from the workspace. - /// - private void RemoveProject_Foreground(AbstractProject project) { AssertIsForeground(); @@ -379,7 +309,7 @@ private void RemoveProject_Foreground(AbstractProject project) Contract.ThrowIfFalse(_projectMap.Remove(project.Id)); } - UpdateProjectBinPath_Foreground(project, project.BinOutputPath, null); + UpdateProjectBinPath(project, project.BinOutputPath, null); using (Dispatcher.CurrentDispatcher.DisableProcessing()) { @@ -392,16 +322,8 @@ private void RemoveProject_Foreground(AbstractProject project) /// /// Updates the project tracker and referencing projects for binary output path change for the given project. - /// If invoked on the foreground thread, the update is executed right away. - /// Otherwise, update is scheduled on foreground task scheduler. /// - /// This method may be called on a background thread. internal void UpdateProjectBinPath(AbstractProject project, string oldBinPathOpt, string newBinPathOpt) - { - ExecuteOrScheduleForegroundAffinitizedAction(() => UpdateProjectBinPath_Foreground(project, oldBinPathOpt, newBinPathOpt)); - } - - internal void UpdateProjectBinPath_Foreground(AbstractProject project, string oldBinPathOpt, string newBinPathOpt) { // UpdateProjectBinPath is defensively executed on the foreground thread as it calls back into referencing projects to perform metadata to P2P reference conversions. AssertIsForeground(); @@ -473,16 +395,9 @@ internal ProjectId GetOrCreateProjectIdForPath(string projectPath, string projec /// /// Notifies the workspace host about the given action. - /// If invoked on the foreground thread, the action is executed right away. - /// Otherwise, the action is scheduled on foreground task scheduler. /// - /// This method may be called on a background thread. + /// This method must be called on the foreground thread. internal void NotifyWorkspaceHosts(Action action) - { - ExecuteOrScheduleForegroundAffinitizedAction(() => NotifyWorkspaceHosts_Foreground(action)); - } - - internal void NotifyWorkspaceHosts_Foreground(Action action) { AssertIsForeground(); diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker_IVsSolutionEvents.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker_IVsSolutionEvents.cs index 96c2d2ba8dfa797045ced488e08058d4fdb2690e..c8d4bc21f157c1c97d9962cd258ea222a03acee3 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker_IVsSolutionEvents.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker_IVsSolutionEvents.cs @@ -97,8 +97,8 @@ int IVsSolutionEvents.OnAfterCloseSolution(object pUnkReserved) Contract.ThrowIfFalse(_projectMap.Count == 0); } - NotifyWorkspaceHosts_Foreground(host => host.OnSolutionRemoved()); - NotifyWorkspaceHosts_Foreground(host => host.ClearSolution()); + NotifyWorkspaceHosts(host => host.OnSolutionRemoved()); + NotifyWorkspaceHosts(host => host.ClearSolution()); lock (_gate) { diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker_IVsSolutionLoadEvents.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker_IVsSolutionLoadEvents.cs index 322268c8c27fe0806b267d5e292ef1645f84fe93..5f2f4a9f2c0f3cc0d01f405b6a0a2b3952906600 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker_IVsSolutionLoadEvents.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker_IVsSolutionLoadEvents.cs @@ -374,7 +374,7 @@ private static string GetLanguageOfProject(string projectFilename) private void FinishLoad() { // We are now completely done, so let's simply ensure all projects are added. - StartPushingToWorkspaceAndNotifyOfOpenDocuments_Foreground(this.ImmutableProjects); + StartPushingToWorkspaceAndNotifyOfOpenDocuments(this.ImmutableProjects); // Also, all remaining project adds need to immediately pushed as well, since we're now "interactive" _solutionLoadComplete = true; diff --git a/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject.cs b/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject.cs index 89fb7f6b62ba1524eb63c86b9de17786b3783306..4b87a065b053d0601422b4ceb388fc6ca9d6c98a 100644 --- a/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject.cs +++ b/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject.cs @@ -58,9 +58,14 @@ private void NormalizeAndSetBinOutputPathAndRelatedData(string binOutputPath) SetBinOutputPathAndRelatedData(binOutputPath); } + + private bool _disconnected; + // We might we invoked from a background thread, so schedule the disconnect on foreground task scheduler. public sealed override void Disconnect() { + _disconnected = true; + if (IsForeground()) { DisconnectCore(); diff --git a/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IWorkspaceProjectContext.cs b/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IWorkspaceProjectContext.cs index 655ff1939f4813683c3a9493f9dd4bf4515b8a4a..b354d48ff0dcdaaebe22e394fe959d0251337edb 100644 --- a/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IWorkspaceProjectContext.cs +++ b/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IWorkspaceProjectContext.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.VisualStudio.LanguageServices.ProjectSystem; using Roslyn.Utilities; @@ -11,6 +13,43 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.C { internal sealed partial class CPSProject : AbstractProject, IWorkspaceProjectContext { + /// + /// Holds the task with continuations to sequentially execute all the foreground affinitized actions on the foreground task scheduler. + /// More specifically, all the notifications to workspace hosts are executed on the foreground thread. However, the project system might make project state changes + /// and request notifications to workspace hosts on background thread. So we queue up all the notifications for project state changes onto this task and execute them on the foreground thread. + /// + private Task _foregroundTaskQueue = Task.CompletedTask; + + /// + /// Controls access to task queue + /// + private readonly object _queueGate = new object(); + + private void ExecuteForegroundAction(Action action) + { + if (IsForeground()) + { + action(); + } + else + { + lock (_queueGate) + { + _foregroundTaskQueue = _foregroundTaskQueue.SafeContinueWith( + _ => + { + // since execution is now technically asynchronous + // only execute action if project is not disconnected and currently being tracked. + if (!_disconnected && this.ProjectTracker.ContainsProject(this)) + { + action(); + } + }, + ForegroundTaskScheduler); + } + } + } + #region Project properties string IWorkspaceProjectContext.DisplayName { @@ -78,45 +117,63 @@ string IWorkspaceProjectContext.BinOutputPath #region Options public void SetOptions(string commandLineForOptions) { - var commandLineArguments = SetArgumentsAndUpdateOptions(commandLineForOptions); - PostSetOptions(commandLineArguments); + ExecuteForegroundAction(() => + { + var commandLineArguments = SetArgumentsAndUpdateOptions(commandLineForOptions); + PostSetOptions(commandLineArguments); + }); } private void PostSetOptions(CommandLineArguments commandLineArguments) { - // Invoke SetOutputPathAndRelatedData to update the project obj output path. - if (commandLineArguments.OutputFileName != null && commandLineArguments.OutputDirectory != null) + ExecuteForegroundAction(() => { - var objOutputPath = PathUtilities.CombinePathsUnchecked(commandLineArguments.OutputDirectory, commandLineArguments.OutputFileName); - SetObjOutputPathAndRelatedData(objOutputPath); - } + // Invoke SetOutputPathAndRelatedData to update the project obj output path. + if (commandLineArguments.OutputFileName != null && commandLineArguments.OutputDirectory != null) + { + var objOutputPath = PathUtilities.CombinePathsUnchecked(commandLineArguments.OutputDirectory, commandLineArguments.OutputFileName); + SetObjOutputPathAndRelatedData(objOutputPath); + } + }); } #endregion #region References public void AddMetadataReference(string referencePath, MetadataReferenceProperties properties) { - referencePath = FileUtilities.NormalizeAbsolutePath(referencePath); - AddMetadataReferenceAndTryConvertingToProjectReferenceIfPossible(referencePath, properties); + ExecuteForegroundAction(() => + { + referencePath = FileUtilities.NormalizeAbsolutePath(referencePath); + AddMetadataReferenceAndTryConvertingToProjectReferenceIfPossible(referencePath, properties); + }); } public new void RemoveMetadataReference(string referencePath) { - referencePath = FileUtilities.NormalizeAbsolutePath(referencePath); - base.RemoveMetadataReference(referencePath); + ExecuteForegroundAction(() => + { + referencePath = FileUtilities.NormalizeAbsolutePath(referencePath); + base.RemoveMetadataReference(referencePath); + }); } public void AddProjectReference(IWorkspaceProjectContext project, MetadataReferenceProperties properties) { - var abstractProject = GetAbstractProject(project); - AddProjectReference(new ProjectReference(abstractProject.Id, properties.Aliases, properties.EmbedInteropTypes)); + ExecuteForegroundAction(() => + { + var abstractProject = GetAbstractProject(project); + AddProjectReference(new ProjectReference(abstractProject.Id, properties.Aliases, properties.EmbedInteropTypes)); + }); } public void RemoveProjectReference(IWorkspaceProjectContext project) { - var referencedProject = GetAbstractProject(project); - var projectReference = GetCurrentProjectReferences().Single(p => p.ProjectId == referencedProject.Id); - RemoveProjectReference(projectReference); + ExecuteForegroundAction(() => + { + var referencedProject = GetAbstractProject(project); + var projectReference = GetCurrentProjectReferences().Single(p => p.ProjectId == referencedProject.Id); + RemoveProjectReference(projectReference); + }); } private AbstractProject GetAbstractProject(IWorkspaceProjectContext project) @@ -134,17 +191,26 @@ private AbstractProject GetAbstractProject(IWorkspaceProjectContext project) #region Files public void AddSourceFile(string filePath, bool isInCurrentContext = true, IEnumerable folderNames = null, SourceCodeKind sourceCodeKind = SourceCodeKind.Regular) { - AddFile(filePath, sourceCodeKind, _ => isInCurrentContext, _ => folderNames.ToImmutableArrayOrEmpty()); + ExecuteForegroundAction(() => + { + AddFile(filePath, sourceCodeKind, _ => isInCurrentContext, _ => folderNames.ToImmutableArrayOrEmpty()); + }); } public void RemoveSourceFile(string filePath) { - RemoveFile(filePath); + ExecuteForegroundAction(() => + { + RemoveFile(filePath); + }); } public void AddAdditionalFile(string filePath, bool isInCurrentContext = true) { - AddAdditionalFile(filePath, getIsInCurrentContext: _ => isInCurrentContext); + ExecuteForegroundAction(() => + { + AddAdditionalFile(filePath, getIsInCurrentContext: _ => isInCurrentContext); + }); } #endregion