提交 a95eee9e 编写于 作者: M Matt Warren 提交者: GitHub

Merge pull request #15852 from mattwar/Bug296548_take2

Push task queue to CPSProject
......@@ -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<ProjectId
protected void RemoveProjectReference(ProjectReference projectReference)
{
AssertIsForeground();
lock (_gate)
{
Contract.ThrowIfFalse(_projectReferences.Remove(projectReference));
......@@ -840,6 +850,8 @@ private static void OnDocumentOpened(object sender, bool isCurrentContext)
IVisualStudioHostDocument document = (IVisualStudioHostDocument)sender;
AbstractProject project = (AbstractProject)document.Project;
project.AssertIsForeground();
if (project._pushingChangesToWorkspaceHosts)
{
project.ProjectTracker.NotifyWorkspaceHosts(host => 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<IVisualStudioHostDocument, bool> getIsCurrentContext,
Func<uint, IReadOnlyList<string>> 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<string> aliases)
{
AssertIsForeground();
file = FileUtilities.NormalizeAbsolutePath(file);
// Have we converted these to project references?
......@@ -1198,6 +1238,8 @@ protected void UpdateMetadataReferenceAliases(string file, ImmutableArray<string
protected void UpdateProjectReferenceAliases(AbstractProject referencedProject, ImmutableArray<string> 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<string> 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<string> GetFolderNamesFromHierarchy(uint documentItemID)
private IReadOnlyList<string> 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))
......
......@@ -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<IVisualStudioHostDocument, bool> 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);
......
......@@ -51,6 +51,8 @@ private void SetArgumentsCore(string commandLine, CommandLineArguments commandLi
/// </summary>
protected void UpdateOptions()
{
AssertIsForeground();
CommandLineArguments lastParsedCommandLineArguments = _lastParsedCommandLineArguments;
Contract.ThrowIfNull(lastParsedCommandLineArguments);
......@@ -70,6 +72,8 @@ protected void UpdateOptions()
/// </summary>
protected CommandLineArguments SetArgumentsAndUpdateOptions(string commandLine)
{
AssertIsForeground();
var commandLineArguments = SetArguments(commandLine);
UpdateOptions();
return commandLineArguments;
......@@ -109,6 +113,8 @@ protected CommandLineArguments SetArguments(string commandLine)
/// </summary>
protected void SetOptions(CompilationOptions newCompilationOptions, ParseOptions newParseOptions)
{
AssertIsForeground();
this.UpdateRuleSetError(this.RuleSetFile);
// Set options.
......
......@@ -65,16 +65,9 @@ internal sealed partial class VisualStudioProjectTracker : ForegroundThreadAffin
/// </summary>
private readonly Dictionary<string, ImmutableArray<AbstractProject>> _projectsByBinPath = new Dictionary<string, ImmutableArray<AbstractProject>>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// 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.
/// </summary>
private Task _taskForForegroundAffinitizedActions = Task.CompletedTask;
private readonly Dictionary<ProjectId, AbstractProject> _projectMap;
private readonly Dictionary<string, ProjectId> _projectPathToIdMap;
#endregion
#endregion
/// <summary>
/// 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);
}
}
/// <summary>
/// If invoked on the foreground thread, the action is executed right away.
/// Otherwise, the action is scheduled on foreground task scheduler.
/// </summary>
/// <param name="action">Action that needs to be executed on a foreground thread.</param>
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)
/// <summary>
/// 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.
/// </summary>
/// <remarks>This method may be called on a background thread.</remarks>
/// <remarks>This method must be called on the foreground thread.</remarks>
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)
/// <summary>
/// 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.
/// </summary>
/// <remarks>This method may be called on a background thread.</remarks>
/// <remarks>This method must be called on the foreground thread.</remarks>
internal void StartPushingToWorkspaceAndNotifyOfOpenDocuments(IEnumerable<AbstractProject> projects)
{
ExecuteOrScheduleForegroundAffinitizedAction(() => StartPushingToWorkspaceAndNotifyOfOpenDocuments_Foreground(projects));
}
private void StartPushingToWorkspaceAndNotifyOfOpenDocuments_Foreground(IEnumerable<AbstractProject> 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.
/// </summary>
internal void RemoveProject(AbstractProject project)
{
ExecuteOrScheduleForegroundAffinitizedAction(() => RemoveProject_Foreground(project));
}
/// <summary>
/// Remove a project from the workspace.
/// </summary>
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)
/// <summary>
/// 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.
/// </summary>
/// <remarks>This method may be called on a background thread.</remarks>
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
/// <summary>
/// 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.
/// </summary>
/// <remarks>This method may be called on a background thread.</remarks>
/// <remarks>This method must be called on the foreground thread.</remarks>
internal void NotifyWorkspaceHosts(Action<IVisualStudioWorkspaceHost> action)
{
ExecuteOrScheduleForegroundAffinitizedAction(() => NotifyWorkspaceHosts_Foreground(action));
}
internal void NotifyWorkspaceHosts_Foreground(Action<IVisualStudioWorkspaceHost> action)
{
AssertIsForeground();
......
......@@ -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)
{
......
......@@ -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;
......
......@@ -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();
......
......@@ -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
{
/// <summary>
/// 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.
/// </summary>
private Task _foregroundTaskQueue = Task.CompletedTask;
/// <summary>
/// Controls access to task queue
/// </summary>
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<string> 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
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册