未验证 提交 258fdd67 编写于 作者: T Tomáš Matoušek 提交者: GitHub

Task queue refactoring (#42610)

* Eliminate WorkspaceTaskScheduler

* Move IAsynchronousOperationListener down to WorkspaceTaskSchedulerFactory

* Move IAsynchronousOperationListener to WorkspaceTaskQueue

* Split WorkspaceTaskSchedulerFactory

* Merge SimpleTaskQueue and WorkspaceTaskQueue into TaskQueue

* Add nullable annotations

* Make cancallation token explicit
上级 9e03a9ac
......@@ -7,6 +7,7 @@
using System;
using System.Composition;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Options;
......@@ -70,18 +71,17 @@ class LegacyEditorConfigDocumentOptionsProviderFactory : IDocumentOptionsProvide
/// An implementation of <see cref="IFileWatcher"/> that ensures we don't watch for a file synchronously to
/// avoid deadlocks.
/// </summary>
internal class DeferredFileWatcher : IFileWatcher
internal sealed class DeferredFileWatcher : IFileWatcher
{
private readonly IFileWatcher _fileWatcher;
private readonly SimpleTaskQueue _taskQueue = new SimpleTaskQueue(TaskScheduler.Default);
private readonly IAsynchronousOperationListener _listener;
private readonly TaskQueue _taskQueue;
public DeferredFileWatcher(IFileWatcher fileWatcher, IAsynchronousOperationListenerProvider asynchronousOperationListenerProvider)
{
_fileWatcher = fileWatcher;
_fileWatcher.ConventionFileChanged += OnConventionFileChangedAsync;
_listener = asynchronousOperationListenerProvider.GetListener(FeatureAttribute.Workspace);
_taskQueue = new TaskQueue(asynchronousOperationListenerProvider.GetListener(FeatureAttribute.Workspace), TaskScheduler.Default);
}
private Task OnConventionFileChangedAsync(object sender, ConventionsFileChangeEventArgs arg)
......@@ -112,12 +112,10 @@ public void Dispose()
public void StartWatching(string fileName, string directoryPath)
{
var asyncToken = _listener.BeginAsyncOperation(nameof(DeferredFileWatcher) + "." + nameof(StartWatching));
// Read the file time stamp right now; we want to know if it changes between now
// and our ability to get the file watcher in place.
var originalFileTimeStamp = TryGetFileTimeStamp(fileName, directoryPath);
_taskQueue.ScheduleTask(() =>
_taskQueue.ScheduleTask(nameof(DeferredFileWatcher) + "." + nameof(StartWatching), () =>
{
_fileWatcher.StartWatching(fileName, directoryPath);
......@@ -143,7 +141,7 @@ public void StartWatching(string fileName, string directoryPath)
ConventionFileChanged?.Invoke(this,
new ConventionsFileChangeEventArgs(fileName, directoryPath, changeType));
}
}).CompletesAsyncOperation(asyncToken);
}, CancellationToken.None);
}
private static DateTime? TryGetFileTimeStamp(string fileName, string directoryPath)
......@@ -168,7 +166,9 @@ public void StartWatching(string fileName, string directoryPath)
public void StopWatching(string fileName, string directoryPath)
{
_taskQueue.ScheduleTask(() => _fileWatcher.StopWatching(fileName, directoryPath));
_taskQueue.ScheduleTask(nameof(DeferredFileWatcher) + "." + nameof(StopWatching),
() => _fileWatcher.StopWatching(fileName, directoryPath),
CancellationToken.None);
}
}
}
......
......@@ -5,11 +5,14 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Options.Providers;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
......@@ -251,11 +254,24 @@ protected internal override HostWorkspaceServices CreateWorkspaceServices(Worksp
}
}
private sealed class MockTaskSchedulerProvider : ITaskSchedulerProvider
{
public TaskScheduler CurrentContextScheduler
=> (SynchronizationContext.Current != null) ? TaskScheduler.FromCurrentSynchronizationContext() : TaskScheduler.Default;
}
private sealed class MockWorkspaceAsynchronousOperationListenerProvider : IWorkspaceAsynchronousOperationListenerProvider
{
public IAsynchronousOperationListener GetListener()
=> AsynchronousOperationListenerProvider.NullListener;
}
private class MockHostWorkspaceServices : HostWorkspaceServices
{
private readonly HostServices _hostServices;
private readonly Workspace _workspace;
private static readonly IWorkspaceTaskSchedulerFactory s_taskSchedulerFactory = new WorkspaceTaskSchedulerFactory();
private static readonly ITaskSchedulerProvider s_taskSchedulerProvider = new MockTaskSchedulerProvider();
private static readonly IWorkspaceAsynchronousOperationListenerProvider s_asyncListenerProvider = new MockWorkspaceAsynchronousOperationListenerProvider();
private readonly OptionServiceFactory.OptionService _optionService;
public MockHostWorkspaceServices(HostServices hostServices, Workspace workspace)
......@@ -278,11 +294,17 @@ public override IEnumerable<TLanguageService> FindLanguageServices<TLanguageServ
public override TWorkspaceService GetService<TWorkspaceService>()
{
if (s_taskSchedulerFactory is TWorkspaceService)
if (s_taskSchedulerProvider is TWorkspaceService)
{
return (TWorkspaceService)s_taskSchedulerFactory;
return (TWorkspaceService)s_taskSchedulerProvider;
}
else if (_optionService is TWorkspaceService workspaceOptionService)
if (s_asyncListenerProvider is TWorkspaceService)
{
return (TWorkspaceService)s_asyncListenerProvider;
}
if (_optionService is TWorkspaceService workspaceOptionService)
{
return workspaceOptionService;
}
......
......@@ -20,8 +20,7 @@ public static Type[] GetLanguageNeutralTypes()
var types = new[]
{
// ROSLYN
typeof(Microsoft.CodeAnalysis.Editor.Implementation.Workspaces.EditorTaskSchedulerFactory),
typeof(Microsoft.CodeAnalysis.Host.WorkspaceTaskSchedulerFactory),
typeof(Microsoft.CodeAnalysis.Host.TaskSchedulerProvider),
typeof(Microsoft.CodeAnalysis.Formatting.Rules.DefaultFormattingRuleFactoryServiceFactory),
typeof(Microsoft.CodeAnalysis.Host.PersistentStorageServiceFactory),
typeof(Microsoft.CodeAnalysis.Text.Implementation.TextBufferFactoryService.TextBufferCloneServiceFactory),
......
......@@ -93,6 +93,7 @@ private static Type[] GetNeutralAndCSharpAndVisualBasicTypes()
typeof(CodeAnalysis.Execution.DesktopReferenceSerializationServiceFactory),
typeof(CodeAnalysis.Execution.SerializerServiceFactory),
typeof(CodeAnalysis.Shared.TestHooks.AsynchronousOperationListenerProvider),
typeof(CodeAnalysis.Host.WorkspaceAsynchronousOperationListenerProvider),
typeof(PrimaryWorkspace),
typeof(TestExportProvider),
typeof(ThreadingContext),
......
......@@ -15,19 +15,20 @@ internal class TestForegroundNotificationService : IForegroundNotificationServic
{
private readonly object _gate = new object();
private readonly List<Task> _tasks = new List<Task>();
private readonly SimpleTaskQueue _queue = new SimpleTaskQueue(TaskScheduler.Default);
private readonly TaskQueue _queue = new TaskQueue(AsynchronousOperationListenerProvider.NullListener, TaskScheduler.Default);
public void RegisterNotification(Func<bool> action, IAsyncToken asyncToken, CancellationToken cancellationToken = default)
{
RegisterNotification(action, 0, asyncToken, cancellationToken);
}
#pragma warning disable CS0618 // Type or member is obsolete (ScheduleTaskInProgress: https://github.com/dotnet/roslyn/issues/42742)
public void RegisterNotification(Func<bool> action, int delayInMS, IAsyncToken asyncToken, CancellationToken cancellationToken = default)
{
Task task;
lock (_gate)
{
task = _queue.ScheduleTask(() => Execute_NoLock(action, asyncToken, cancellationToken), cancellationToken);
task = _queue.ScheduleTaskInProgress(() => Execute_NoLock(action, asyncToken, cancellationToken), cancellationToken);
_tasks.Add(task);
}
......@@ -42,7 +43,7 @@ private void Execute_NoLock(Func<bool> action, IAsyncToken asyncToken, Cancellat
}
else
{
_tasks.Add(_queue.ScheduleTask(() => Execute_NoLock(action, asyncToken, cancellationToken), cancellationToken));
_tasks.Add(_queue.ScheduleTaskInProgress(() => Execute_NoLock(action, asyncToken, cancellationToken), cancellationToken));
}
}
......@@ -56,15 +57,13 @@ public void RegisterNotification(Action action, int delayInMS, IAsyncToken async
Task task;
lock (_gate)
{
task = _queue.ScheduleTask(() =>
{
action();
}, cancellationToken).CompletesAsyncOperation(asyncToken);
task = _queue.ScheduleTaskInProgress(action, cancellationToken).CompletesAsyncOperation(asyncToken);
_tasks.Add(task);
}
task.Wait(cancellationToken);
}
#pragma warning restore
}
}
......@@ -10,8 +10,10 @@
using System.Composition;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics.EngineV2;
using Microsoft.CodeAnalysis.Diagnostics.Log;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.SolutionCrawler;
......@@ -24,6 +26,14 @@ namespace Microsoft.CodeAnalysis.Diagnostics
[Shared]
internal partial class DiagnosticAnalyzerService : IDiagnosticAnalyzerService
{
private const string DiagnosticsUpdatedEventName = "DiagnosticsUpdated";
private static readonly DiagnosticEventTaskScheduler s_eventScheduler = new DiagnosticEventTaskScheduler(blockingUpperBound: 100);
// use eventMap and taskQueue to serialize events
private readonly EventMap _eventMap;
private readonly TaskQueue _eventQueue;
public DiagnosticAnalyzerInfoCache AnalyzerInfoCache { get; private set; }
public HostDiagnosticAnalyzers HostAnalyzers { get; private set; }
......@@ -31,6 +41,9 @@ internal partial class DiagnosticAnalyzerService : IDiagnosticAnalyzerService
public IAsynchronousOperationListener Listener { get; }
private readonly ConditionalWeakTable<Workspace, DiagnosticIncrementalAnalyzer> _map;
private readonly ConditionalWeakTable<Workspace, DiagnosticIncrementalAnalyzer>.CreateValueCallback _createIncrementalAnalyzer;
[ImportingConstructor]
public DiagnosticAnalyzerService(
IDiagnosticUpdateSourceRegistrationService registrationService,
......@@ -72,12 +85,22 @@ internal partial class DiagnosticAnalyzerService : IDiagnosticAnalyzerService
AbstractHostDiagnosticUpdateSource? hostDiagnosticUpdateSource,
IDiagnosticUpdateSourceRegistrationService registrationService,
IAsynchronousOperationListener? listener = null)
: this(registrationService)
{
AnalyzerInfoCache = analyzerInfoCache;
HostAnalyzers = hostAnalyzers;
_hostDiagnosticUpdateSource = hostDiagnosticUpdateSource;
_map = new ConditionalWeakTable<Workspace, DiagnosticIncrementalAnalyzer>();
_createIncrementalAnalyzer = CreateIncrementalAnalyzerCallback;
Listener = listener ?? AsynchronousOperationListenerProvider.NullListener;
_eventMap = new EventMap();
// use diagnostic event task scheduler so that we never flood async events queue with million of events.
// queue itself can handle huge number of events but we are seeing OOM due to captured data in pending events.
_eventQueue = new TaskQueue(Listener, s_eventScheduler);
registrationService.Register(this);
}
private static ImmutableArray<HostDiagnosticAnalyzerPackage> GetHostDiagnosticAnalyzerPackage(IHostDiagnosticAnalyzerPackageProvider? diagnosticAnalyzerProviderService)
......
......@@ -17,16 +17,6 @@ namespace Microsoft.CodeAnalysis.Diagnostics
workspaceKinds: new string[] { WorkspaceKind.Host, WorkspaceKind.Interactive, WorkspaceKind.AnyCodeRoslynWorkspace })]
internal partial class DiagnosticAnalyzerService : IIncrementalAnalyzerProvider
{
private readonly ConditionalWeakTable<Workspace, DiagnosticIncrementalAnalyzer> _map;
private readonly ConditionalWeakTable<Workspace, DiagnosticIncrementalAnalyzer>.CreateValueCallback _createIncrementalAnalyzer;
[SuppressMessage("RoslyDiagnosticsReliability", "RS0034:Exported parts should have [ImportingConstructor]", Justification = "Private constructor used for deterministic field initialization")]
private DiagnosticAnalyzerService()
{
_map = new ConditionalWeakTable<Workspace, DiagnosticIncrementalAnalyzer>();
_createIncrementalAnalyzer = CreateIncrementalAnalyzerCallback;
}
public IIncrementalAnalyzer CreateIncrementalAnalyzer(Workspace workspace)
{
if (!workspace.Options.GetOption(ServiceComponentOnOffOptions.DiagnosticProvider))
......
......@@ -14,26 +14,6 @@ namespace Microsoft.CodeAnalysis.Diagnostics
{
internal partial class DiagnosticAnalyzerService : IDiagnosticUpdateSource
{
private const string DiagnosticsUpdatedEventName = "DiagnosticsUpdated";
private static readonly DiagnosticEventTaskScheduler s_eventScheduler = new DiagnosticEventTaskScheduler(blockingUpperBound: 100);
// use eventMap and taskQueue to serialize events
private readonly EventMap _eventMap;
private readonly SimpleTaskQueue _eventQueue;
[SuppressMessage("RoslyDiagnosticsReliability", "RS0034:Exported parts should have [ImportingConstructor]", Justification = "Private constructor used for deterministic field initialization")]
private DiagnosticAnalyzerService(IDiagnosticUpdateSourceRegistrationService registrationService) : this()
{
_eventMap = new EventMap();
// use diagnostic event task scheduler so that we never flood async events queue with million of events.
// queue itself can handle huge number of events but we are seeing OOM due to captured data in pending events.
_eventQueue = new SimpleTaskQueue(s_eventScheduler);
registrationService.Register(this);
}
public event EventHandler<DiagnosticsUpdatedArgs> DiagnosticsUpdated
{
add
......@@ -66,8 +46,7 @@ internal void RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs args)
var ev = _eventMap.GetEventHandlers<EventHandler<DiagnosticsUpdatedArgs>>(DiagnosticsUpdatedEventName);
if (ev.HasHandlers)
{
var asyncToken = Listener.BeginAsyncOperation(nameof(RaiseDiagnosticsUpdated));
_eventQueue.ScheduleTask(() => ev.RaiseEvent(handler => handler(this, args))).CompletesAsyncOperation(asyncToken);
_eventQueue.ScheduleTask(nameof(RaiseDiagnosticsUpdated), () => ev.RaiseEvent(handler => handler(this, args)), CancellationToken.None);
}
}
......@@ -82,8 +61,7 @@ internal void RaiseBulkDiagnosticsUpdated(Action<Action<DiagnosticsUpdatedArgs>>
// this is to reduce for such case to happen.
void raiseEvents(DiagnosticsUpdatedArgs args) => ev.RaiseEvent(handler => handler(this, args));
var asyncToken = Listener.BeginAsyncOperation(nameof(RaiseDiagnosticsUpdated));
_eventQueue.ScheduleTask(() => eventAction(raiseEvents)).CompletesAsyncOperation(asyncToken);
_eventQueue.ScheduleTask(nameof(RaiseDiagnosticsUpdated), () => eventAction(raiseEvents), CancellationToken.None);
}
}
......@@ -98,8 +76,7 @@ internal void RaiseBulkDiagnosticsUpdated(Func<Action<DiagnosticsUpdatedArgs>, T
// this is to reduce for such case to happen.
void raiseEvents(DiagnosticsUpdatedArgs args) => ev.RaiseEvent(handler => handler(this, args));
var asyncToken = Listener.BeginAsyncOperation(nameof(RaiseDiagnosticsUpdated));
_eventQueue.ScheduleTask(() => eventActionAsync(raiseEvents)).CompletesAsyncOperation(asyncToken);
_eventQueue.ScheduleTask(nameof(RaiseDiagnosticsUpdated), () => eventActionAsync(raiseEvents), CancellationToken.None);
}
}
......
......@@ -23,9 +23,8 @@ internal partial class DiagnosticService : IDiagnosticService
private static readonly DiagnosticEventTaskScheduler s_eventScheduler = new DiagnosticEventTaskScheduler(blockingUpperBound: 100);
private readonly IAsynchronousOperationListener _listener;
private readonly EventMap _eventMap;
private readonly SimpleTaskQueue _eventQueue;
private readonly TaskQueue _eventQueue;
private readonly object _gate;
private readonly Dictionary<IDiagnosticUpdateSource, Dictionary<Workspace, Dictionary<object, Data>>> _map;
......@@ -42,9 +41,7 @@ internal partial class DiagnosticService : IDiagnosticService
// use diagnostic event task scheduler so that we never flood async events queue with million of events.
// queue itself can handle huge number of events but we are seeing OOM due to captured data in pending events.
_eventQueue = new SimpleTaskQueue(s_eventScheduler);
_listener = listenerProvider.GetListener(FeatureAttribute.DiagnosticService);
_eventQueue = new TaskQueue(listenerProvider.GetListener(FeatureAttribute.DiagnosticService), s_eventScheduler);
_gate = new object();
_map = new Dictionary<IDiagnosticUpdateSource, Dictionary<Workspace, Dictionary<object, Data>>>();
......@@ -71,8 +68,7 @@ private void RaiseDiagnosticsUpdated(IDiagnosticUpdateSource source, Diagnostics
var ev = _eventMap.GetEventHandlers<EventHandler<DiagnosticsUpdatedArgs>>(DiagnosticsUpdatedEventName);
var eventToken = _listener.BeginAsyncOperation(DiagnosticsUpdatedEventName);
_eventQueue.ScheduleTask(() =>
_eventQueue.ScheduleTask(DiagnosticsUpdatedEventName, () =>
{
if (!UpdateDataMap(source, args))
{
......@@ -81,15 +77,14 @@ private void RaiseDiagnosticsUpdated(IDiagnosticUpdateSource source, Diagnostics
}
ev.RaiseEvent(handler => handler(source, args));
}).CompletesAsyncOperation(eventToken);
}, CancellationToken.None);
}
private void RaiseDiagnosticsCleared(IDiagnosticUpdateSource source)
{
var ev = _eventMap.GetEventHandlers<EventHandler<DiagnosticsUpdatedArgs>>(DiagnosticsUpdatedEventName);
var eventToken = _listener.BeginAsyncOperation(DiagnosticsUpdatedEventName);
_eventQueue.ScheduleTask(() =>
_eventQueue.ScheduleTask(DiagnosticsUpdatedEventName, () =>
{
using var pooledObject = SharedPools.Default<List<DiagnosticsUpdatedArgs>>().GetPooledObject();
......@@ -106,7 +101,7 @@ private void RaiseDiagnosticsCleared(IDiagnosticUpdateSource source)
{
ev.RaiseEvent(handler => handler(source, args));
}
}).CompletesAsyncOperation(eventToken);
}, CancellationToken.None);
}
private bool UpdateDataMap(IDiagnosticUpdateSource source, DiagnosticsUpdatedArgs args)
......
......@@ -32,7 +32,7 @@ private partial class WorkCoordinator
private readonly CancellationTokenSource _shutdownNotificationSource;
private readonly CancellationToken _shutdownToken;
private readonly SimpleTaskQueue _eventProcessingQueue;
private readonly TaskQueue _eventProcessingQueue;
// points to processor task
private readonly IncrementalAnalyzerProcessor _documentAndProjectWorkerProcessor;
......@@ -59,7 +59,7 @@ private partial class WorkCoordinator
_shutdownNotificationSource = new CancellationTokenSource();
_shutdownToken = _shutdownNotificationSource.Token;
_eventProcessingQueue = new SimpleTaskQueue(TaskScheduler.Default);
_eventProcessingQueue = new TaskQueue(listener, TaskScheduler.Default);
var activeFileBackOffTimeSpanInMS = _optionService.GetOption(InternalSolutionCrawlerOptions.ActiveFileWorkerBackOffTimeSpanInMS);
var allFilesWorkerBackOffTimeSpanInMS = _optionService.GetOption(InternalSolutionCrawlerOptions.AllFilesWorkerBackOffTimeSpanInMS);
......@@ -179,13 +179,11 @@ private void ReanalyzeOnOptionChange(object sender, OptionChangedEventArgs e)
{
// get off from option changed event handler since it runs on UI thread
// getting analyzer can be slow for the very first time since it is lazily initialized
var asyncToken = _listener.BeginAsyncOperation("ReanalyzeOnOptionChange");
// Force analyze all analyzers if background analysis scope has changed.
var forceAnalyze = e.Option == SolutionCrawlerOptions.BackgroundAnalysisScopeOption;
_eventProcessingQueue.ScheduleTask(() =>
_eventProcessingQueue.ScheduleTask(nameof(ReanalyzeOnOptionChange), () =>
{
// Force analyze all analyzers if background analysis scope has changed.
var forceAnalyze = e.Option == SolutionCrawlerOptions.BackgroundAnalysisScopeOption;
// let each analyzer decide what they want on option change
foreach (var analyzer in _documentAndProjectWorkerProcessor.Analyzers)
{
......@@ -195,14 +193,13 @@ private void ReanalyzeOnOptionChange(object sender, OptionChangedEventArgs e)
Reanalyze(analyzer, scope);
}
}
}, _shutdownToken).CompletesAsyncOperation(asyncToken);
}, _shutdownToken);
}
public void Reanalyze(IIncrementalAnalyzer analyzer, ReanalyzeScope scope, bool highPriority = false)
{
var asyncToken = _listener.BeginAsyncOperation("Reanalyze");
_eventProcessingQueue.ScheduleTask(
() => EnqueueWorkItemAsync(analyzer, scope, highPriority), _shutdownToken).CompletesAsyncOperation(asyncToken);
_eventProcessingQueue.ScheduleTask("Reanalyze",
() => EnqueueWorkItemAsync(analyzer, scope, highPriority), _shutdownToken);
if (scope.HasMultipleDocuments)
{
......@@ -216,7 +213,6 @@ public void Reanalyze(IIncrementalAnalyzer analyzer, ReanalyzeScope scope, bool
private void OnActiveDocumentChanged(object sender, DocumentId activeDocumentId)
{
IAsyncToken asyncToken;
var solution = _registration.Workspace.CurrentSolution;
// Check if we are only performing backgroung analysis for active file.
......@@ -237,15 +233,13 @@ private void OnActiveDocumentChanged(object sender, DocumentId activeDocumentId)
{
if (_lastActiveDocument != null)
{
asyncToken = _listener.BeginAsyncOperation("OnDocumentClosed");
EnqueueEvent(_lastActiveDocument.Project.Solution, _lastActiveDocument.Id, InvocationReasons.DocumentClosed, asyncToken);
EnqueueEvent(_lastActiveDocument.Project.Solution, _lastActiveDocument.Id, InvocationReasons.DocumentClosed, "OnDocumentClosed");
}
_lastActiveDocument = activeDocument;
}
asyncToken = _listener.BeginAsyncOperation("OnDocumentOpened");
EnqueueEvent(activeDocument.Project.Solution, activeDocument.Id, InvocationReasons.DocumentOpened, asyncToken);
EnqueueEvent(activeDocument.Project.Solution, activeDocument.Id, InvocationReasons.DocumentOpened, "OnDocumentOpened");
}
}
}
......@@ -255,7 +249,7 @@ private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs args)
// guard us from cancellation
try
{
ProcessEvents(args, _listener.BeginAsyncOperation("OnWorkspaceChanged"));
ProcessEvent(args, "OnWorkspaceChanged");
}
catch (OperationCanceledException oce)
{
......@@ -287,7 +281,7 @@ private bool NotOurShutdownToken(OperationCanceledException oce)
return oce.CancellationToken == _shutdownToken;
}
private void ProcessEvents(WorkspaceChangeEventArgs args, IAsyncToken asyncToken)
private void ProcessEvent(WorkspaceChangeEventArgs args, string eventName)
{
SolutionCrawlerLogger.LogWorkspaceEvent(_logAggregator, (int)args.Kind);
......@@ -299,13 +293,13 @@ private void ProcessEvents(WorkspaceChangeEventArgs args, IAsyncToken asyncToken
case WorkspaceChangeKind.SolutionReloaded:
case WorkspaceChangeKind.SolutionRemoved:
case WorkspaceChangeKind.SolutionCleared:
ProcessSolutionEvent(args, asyncToken);
ProcessSolutionEvent(args, eventName);
break;
case WorkspaceChangeKind.ProjectAdded:
case WorkspaceChangeKind.ProjectChanged:
case WorkspaceChangeKind.ProjectReloaded:
case WorkspaceChangeKind.ProjectRemoved:
ProcessProjectEvent(args, asyncToken);
ProcessProjectEvent(args, eventName);
break;
case WorkspaceChangeKind.DocumentAdded:
case WorkspaceChangeKind.DocumentReloaded:
......@@ -319,7 +313,7 @@ private void ProcessEvents(WorkspaceChangeEventArgs args, IAsyncToken asyncToken
case WorkspaceChangeKind.AnalyzerConfigDocumentRemoved:
case WorkspaceChangeKind.AnalyzerConfigDocumentChanged:
case WorkspaceChangeKind.AnalyzerConfigDocumentReloaded:
ProcessDocumentEvent(args, asyncToken);
ProcessDocumentEvent(args, eventName);
break;
default:
throw ExceptionUtilities.UnexpectedValue(args.Kind);
......@@ -328,31 +322,29 @@ private void ProcessEvents(WorkspaceChangeEventArgs args, IAsyncToken asyncToken
private void OnDocumentOpened(object sender, DocumentEventArgs e)
{
var asyncToken = _listener.BeginAsyncOperation("OnDocumentOpened");
_eventProcessingQueue.ScheduleTask(
() => EnqueueWorkItemAsync(e.Document, InvocationReasons.DocumentOpened), _shutdownToken).CompletesAsyncOperation(asyncToken);
_eventProcessingQueue.ScheduleTask("OnDocumentOpened",
() => EnqueueWorkItemAsync(e.Document, InvocationReasons.DocumentOpened), _shutdownToken);
}
private void OnDocumentClosed(object sender, DocumentEventArgs e)
{
var asyncToken = _listener.BeginAsyncOperation("OnDocumentClosed");
_eventProcessingQueue.ScheduleTask(
() => EnqueueWorkItemAsync(e.Document, InvocationReasons.DocumentClosed), _shutdownToken).CompletesAsyncOperation(asyncToken);
_eventProcessingQueue.ScheduleTask("OnDocumentClosed",
() => EnqueueWorkItemAsync(e.Document, InvocationReasons.DocumentClosed), _shutdownToken);
}
private void ProcessDocumentEvent(WorkspaceChangeEventArgs e, IAsyncToken asyncToken)
private void ProcessDocumentEvent(WorkspaceChangeEventArgs e, string eventName)
{
switch (e.Kind)
{
case WorkspaceChangeKind.DocumentAdded:
EnqueueEvent(e.NewSolution, e.DocumentId, InvocationReasons.DocumentAdded, asyncToken);
EnqueueEvent(e.NewSolution, e.DocumentId, InvocationReasons.DocumentAdded, eventName);
break;
case WorkspaceChangeKind.DocumentRemoved:
EnqueueEvent(e.OldSolution, e.DocumentId, InvocationReasons.DocumentRemoved, asyncToken);
EnqueueEvent(e.OldSolution, e.DocumentId, InvocationReasons.DocumentRemoved, eventName);
break;
case WorkspaceChangeKind.DocumentReloaded:
case WorkspaceChangeKind.DocumentChanged:
EnqueueEvent(e.OldSolution, e.NewSolution, e.DocumentId, asyncToken);
EnqueueEvent(e.OldSolution, e.NewSolution, e.DocumentId, eventName);
break;
case WorkspaceChangeKind.AdditionalDocumentAdded:
......@@ -364,7 +356,7 @@ private void ProcessDocumentEvent(WorkspaceChangeEventArgs e, IAsyncToken asyncT
case WorkspaceChangeKind.AnalyzerConfigDocumentChanged:
case WorkspaceChangeKind.AnalyzerConfigDocumentReloaded:
// If an additional file or .editorconfig has changed we need to reanalyze the entire project.
EnqueueEvent(e.NewSolution, e.ProjectId, InvocationReasons.AdditionalDocumentChanged, asyncToken);
EnqueueEvent(e.NewSolution, e.ProjectId, InvocationReasons.AdditionalDocumentChanged, eventName);
break;
default:
......@@ -372,82 +364,82 @@ private void ProcessDocumentEvent(WorkspaceChangeEventArgs e, IAsyncToken asyncT
}
}
private void ProcessProjectEvent(WorkspaceChangeEventArgs e, IAsyncToken asyncToken)
private void ProcessProjectEvent(WorkspaceChangeEventArgs e, string eventName)
{
switch (e.Kind)
{
case WorkspaceChangeKind.ProjectAdded:
EnqueueEvent(e.NewSolution, e.ProjectId, InvocationReasons.DocumentAdded, asyncToken);
EnqueueEvent(e.NewSolution, e.ProjectId, InvocationReasons.DocumentAdded, eventName);
break;
case WorkspaceChangeKind.ProjectRemoved:
EnqueueEvent(e.OldSolution, e.ProjectId, InvocationReasons.DocumentRemoved, asyncToken);
EnqueueEvent(e.OldSolution, e.ProjectId, InvocationReasons.DocumentRemoved, eventName);
break;
case WorkspaceChangeKind.ProjectChanged:
case WorkspaceChangeKind.ProjectReloaded:
EnqueueEvent(e.OldSolution, e.NewSolution, e.ProjectId, asyncToken);
EnqueueEvent(e.OldSolution, e.NewSolution, e.ProjectId, eventName);
break;
default:
throw ExceptionUtilities.UnexpectedValue(e.Kind);
}
}
private void ProcessSolutionEvent(WorkspaceChangeEventArgs e, IAsyncToken asyncToken)
private void ProcessSolutionEvent(WorkspaceChangeEventArgs e, string eventName)
{
switch (e.Kind)
{
case WorkspaceChangeKind.SolutionAdded:
EnqueueEvent(e.NewSolution, InvocationReasons.DocumentAdded, asyncToken);
EnqueueEvent(e.NewSolution, InvocationReasons.DocumentAdded, eventName);
break;
case WorkspaceChangeKind.SolutionRemoved:
EnqueueEvent(e.OldSolution, InvocationReasons.SolutionRemoved, asyncToken);
EnqueueEvent(e.OldSolution, InvocationReasons.SolutionRemoved, eventName);
break;
case WorkspaceChangeKind.SolutionCleared:
EnqueueEvent(e.OldSolution, InvocationReasons.DocumentRemoved, asyncToken);
EnqueueEvent(e.OldSolution, InvocationReasons.DocumentRemoved, eventName);
break;
case WorkspaceChangeKind.SolutionChanged:
case WorkspaceChangeKind.SolutionReloaded:
EnqueueEvent(e.OldSolution, e.NewSolution, asyncToken);
EnqueueEvent(e.OldSolution, e.NewSolution, eventName);
break;
default:
throw ExceptionUtilities.UnexpectedValue(e.Kind);
}
}
private void EnqueueEvent(Solution oldSolution, Solution newSolution, IAsyncToken asyncToken)
private void EnqueueEvent(Solution oldSolution, Solution newSolution, string eventName)
{
_eventProcessingQueue.ScheduleTask(
() => EnqueueWorkItemAsync(oldSolution, newSolution), _shutdownToken).CompletesAsyncOperation(asyncToken);
_eventProcessingQueue.ScheduleTask(eventName,
() => EnqueueWorkItemAsync(oldSolution, newSolution), _shutdownToken);
}
private void EnqueueEvent(Solution solution, InvocationReasons invocationReasons, IAsyncToken asyncToken)
private void EnqueueEvent(Solution solution, InvocationReasons invocationReasons, string eventName)
{
_eventProcessingQueue.ScheduleTask(
() => EnqueueWorkItemForSolutionAsync(solution, invocationReasons), _shutdownToken).CompletesAsyncOperation(asyncToken);
_eventProcessingQueue.ScheduleTask(eventName,
() => EnqueueWorkItemForSolutionAsync(solution, invocationReasons), _shutdownToken);
}
private void EnqueueEvent(Solution oldSolution, Solution newSolution, ProjectId projectId, IAsyncToken asyncToken)
private void EnqueueEvent(Solution oldSolution, Solution newSolution, ProjectId projectId, string eventName)
{
_eventProcessingQueue.ScheduleTask(
() => EnqueueWorkItemAfterDiffAsync(oldSolution, newSolution, projectId), _shutdownToken).CompletesAsyncOperation(asyncToken);
_eventProcessingQueue.ScheduleTask(eventName,
() => EnqueueWorkItemAfterDiffAsync(oldSolution, newSolution, projectId), _shutdownToken);
}
private void EnqueueEvent(Solution solution, ProjectId projectId, InvocationReasons invocationReasons, IAsyncToken asyncToken)
private void EnqueueEvent(Solution solution, ProjectId projectId, InvocationReasons invocationReasons, string eventName)
{
_eventProcessingQueue.ScheduleTask(
() => EnqueueWorkItemForProjectAsync(solution, projectId, invocationReasons), _shutdownToken).CompletesAsyncOperation(asyncToken);
_eventProcessingQueue.ScheduleTask(eventName,
() => EnqueueWorkItemForProjectAsync(solution, projectId, invocationReasons), _shutdownToken);
}
private void EnqueueEvent(Solution solution, DocumentId documentId, InvocationReasons invocationReasons, IAsyncToken asyncToken)
private void EnqueueEvent(Solution solution, DocumentId documentId, InvocationReasons invocationReasons, string eventName)
{
_eventProcessingQueue.ScheduleTask(
() => EnqueueWorkItemForDocumentAsync(solution, documentId, invocationReasons), _shutdownToken).CompletesAsyncOperation(asyncToken);
_eventProcessingQueue.ScheduleTask(eventName,
() => EnqueueWorkItemForDocumentAsync(solution, documentId, invocationReasons), _shutdownToken);
}
private void EnqueueEvent(Solution oldSolution, Solution newSolution, DocumentId documentId, IAsyncToken asyncToken)
private void EnqueueEvent(Solution oldSolution, Solution newSolution, DocumentId documentId, string eventName)
{
// document changed event is the special one.
_eventProcessingQueue.ScheduleTask(
() => EnqueueWorkItemAfterDiffAsync(oldSolution, newSolution, documentId), _shutdownToken).CompletesAsyncOperation(asyncToken);
_eventProcessingQueue.ScheduleTask(eventName,
() => EnqueueWorkItemAfterDiffAsync(oldSolution, newSolution, documentId), _shutdownToken);
}
private async Task EnqueueWorkItemAsync(Document document, InvocationReasons invocationReasons, SyntaxNode changedMember = null)
......
......@@ -14,10 +14,10 @@
namespace Microsoft.CodeAnalysis.Host
{
internal class BackgroundCompiler : IDisposable
internal sealed class BackgroundCompiler : IDisposable
{
private Workspace _workspace;
private readonly IWorkspaceTaskScheduler _compilationScheduler;
private readonly TaskQueue _taskQueue;
// Used to keep a strong reference to the built compilations so they are not GC'd
private Compilation[] _mostRecentCompilations;
......@@ -30,8 +30,8 @@ public BackgroundCompiler(Workspace workspace)
_workspace = workspace;
// make a scheduler that runs on the thread pool
var taskSchedulerFactory = workspace.Services.GetService<IWorkspaceTaskSchedulerFactory>();
_compilationScheduler = taskSchedulerFactory.CreateBackgroundTaskScheduler();
var listenerProvider = workspace.Services.GetRequiredService<IWorkspaceAsynchronousOperationListenerProvider>();
_taskQueue = new TaskQueue(listenerProvider.GetListener(), TaskScheduler.Default);
_cancellationSource = new CancellationTokenSource();
_workspace.WorkspaceChanged += OnWorkspaceChanged;
......@@ -131,9 +131,9 @@ private void CancelBuild(bool releasePreviousCompilations)
ISet<ProjectId> allProjects)
{
var cancellationToken = _cancellationSource.Token;
return _compilationScheduler.ScheduleTask(
() => BuildCompilationsAsync(solution, initialProject, allProjects, cancellationToken),
return _taskQueue.ScheduleTask(
"BackgroundCompiler.BuildCompilationsAsync",
() => BuildCompilationsAsync(solution, initialProject, allProjects, cancellationToken),
cancellationToken);
}
......
......@@ -21,10 +21,10 @@ namespace Microsoft.CodeAnalysis.Host
/// but certain host such as VS, we have this (BackgroundParser) which preemptively
/// trying to realize such trees for open/active files expecting users will use them soonish.
/// </summary>
internal class BackgroundParser
internal sealed class BackgroundParser
{
private readonly Workspace _workspace;
private readonly IWorkspaceTaskScheduler _taskScheduler;
private readonly TaskQueue _taskQueue;
private readonly IDocumentTrackingService _documentTrackingService;
private readonly ReaderWriterLockSlim _stateLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
......@@ -38,8 +38,8 @@ public BackgroundParser(Workspace workspace)
{
_workspace = workspace;
var taskSchedulerFactory = workspace.Services.GetService<IWorkspaceTaskSchedulerFactory>();
_taskScheduler = taskSchedulerFactory.CreateBackgroundTaskScheduler();
var listenerProvider = workspace.Services.GetRequiredService<IWorkspaceAsynchronousOperationListenerProvider>();
_taskQueue = new TaskQueue(listenerProvider.GetListener(), TaskScheduler.Default);
_documentTrackingService = workspace.Services.GetService<IDocumentTrackingService>();
......@@ -212,9 +212,9 @@ private Task ParseDocumentAsync(Document document)
// By not cancelling, we can reuse the useful results of previous tasks when performing later steps in the chain.
//
// we still cancel whole task if the task didn't start yet. we just don't cancel if task is started but not finished yet.
var task = _taskScheduler.ScheduleTask(
() => document.GetSyntaxTreeAsync(CancellationToken.None),
var task = _taskQueue.ScheduleTask(
"BackgroundParser.ParseDocumentAsync",
() => document.GetSyntaxTreeAsync(CancellationToken.None),
cancellationToken);
// Always ensure that we mark this work as done from the workmap.
......
......@@ -19,7 +19,7 @@ internal partial class RemoteHostClientServiceFactory
private class SolutionChecksumUpdater : GlobalOperationAwareIdleProcessor
{
private readonly RemoteHostClientService _service;
private readonly SimpleTaskQueue _textChangeQueue;
private readonly TaskQueue _textChangeQueue;
private readonly SemaphoreSlim _event;
private readonly object _gate;
......@@ -34,7 +34,7 @@ public SolutionChecksumUpdater(RemoteHostClientService service, CancellationToke
service.Workspace.Options.GetOption(RemoteHostOptions.SolutionChecksumMonitorBackOffTimeSpanInMS), shutdownToken)
{
_service = service;
_textChangeQueue = new SimpleTaskQueue(TaskScheduler.Default);
_textChangeQueue = new TaskQueue(service.Listener, TaskScheduler.Default);
_event = new SemaphoreSlim(initialCount: 0);
_gate = new object();
......@@ -199,8 +199,7 @@ private void PushTextChanges(Document oldDocument, Document newDocument)
}
// only cancelled when remote host gets shutdown
var token = Listener.BeginAsyncOperation(nameof(PushTextChanges));
_textChangeQueue.ScheduleTask(async () =>
_textChangeQueue.ScheduleTask(nameof(PushTextChanges), async () =>
{
var client = await RemoteHostClient.TryGetClientAsync(_service.Workspace, CancellationToken).ConfigureAwait(false);
if (client == null)
......@@ -218,7 +217,7 @@ private void PushTextChanges(Document oldDocument, Document newDocument)
callbackTarget: null,
CancellationToken).ConfigureAwait(false);
}, CancellationToken).CompletesAsyncOperation(token);
}, CancellationToken);
}
}
}
......
......@@ -26,8 +26,7 @@ internal sealed class ExternalErrorDiagnosticUpdateSource : IDiagnosticUpdateSou
private readonly IDiagnosticAnalyzerService _diagnosticService;
private readonly IGlobalOperationNotificationService _notificationService;
private readonly SimpleTaskQueue _taskQueue;
private readonly IAsynchronousOperationListener _listener;
private readonly TaskQueue _taskQueue;
private readonly object _gate = new object();
private InProgressState _stateDoNotAccessDirectly = null;
......@@ -52,8 +51,7 @@ internal sealed class ExternalErrorDiagnosticUpdateSource : IDiagnosticUpdateSou
IAsynchronousOperationListener listener)
{
// use queue to serialize work. no lock needed
_taskQueue = new SimpleTaskQueue(TaskScheduler.Default);
_listener = listener;
_taskQueue = new TaskQueue(listener, TaskScheduler.Default);
_workspace = workspace;
_workspace.WorkspaceChanged += OnWorkspaceChanged;
......@@ -84,8 +82,7 @@ public void ClearErrors(ProjectId projectId)
// capture state if it exists
var state = BuildInprogressState;
var asyncToken = _listener.BeginAsyncOperation("ClearErrors");
_taskQueue.ScheduleTask(() =>
_taskQueue.ScheduleTask(nameof(ClearErrors), () =>
{
// this will get called if the project is actually built by "build" command.
// we track what project has been built, so that later we can clear any stale live errors
......@@ -93,7 +90,7 @@ public void ClearErrors(ProjectId projectId)
state?.Built(projectId);
ClearProjectErrors(state?.Solution ?? _workspace.CurrentSolution, projectId);
}).CompletesAsyncOperation(asyncToken);
}, CancellationToken.None);
}
private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e)
......@@ -104,27 +101,18 @@ private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e)
case WorkspaceChangeKind.SolutionRemoved:
case WorkspaceChangeKind.SolutionCleared:
case WorkspaceChangeKind.SolutionReloaded:
{
var asyncToken = _listener.BeginAsyncOperation("OnSolutionChanged");
_taskQueue.ScheduleTask(() => e.OldSolution.ProjectIds.Do(p => ClearProjectErrors(e.OldSolution, p))).CompletesAsyncOperation(asyncToken);
break;
}
_taskQueue.ScheduleTask("OnSolutionChanged", () => e.OldSolution.ProjectIds.Do(p => ClearProjectErrors(e.OldSolution, p)), CancellationToken.None);
break;
case WorkspaceChangeKind.ProjectRemoved:
case WorkspaceChangeKind.ProjectReloaded:
{
var asyncToken = _listener.BeginAsyncOperation("OnProjectChanged");
_taskQueue.ScheduleTask(() => ClearProjectErrors(e.OldSolution, e.ProjectId)).CompletesAsyncOperation(asyncToken);
break;
}
_taskQueue.ScheduleTask("OnProjectChanged", () => ClearProjectErrors(e.OldSolution, e.ProjectId), CancellationToken.None);
break;
case WorkspaceChangeKind.DocumentRemoved:
case WorkspaceChangeKind.DocumentReloaded:
{
var asyncToken = _listener.BeginAsyncOperation("OnDocumentRemoved");
_taskQueue.ScheduleTask(() => ClearDocumentErrors(e.OldSolution, e.ProjectId, e.DocumentId)).CompletesAsyncOperation(asyncToken);
break;
}
_taskQueue.ScheduleTask("OnDocumentRemoved", () => ClearDocumentErrors(e.OldSolution, e.ProjectId, e.DocumentId), CancellationToken.None);
break;
case WorkspaceChangeKind.ProjectAdded:
case WorkspaceChangeKind.DocumentAdded:
......@@ -140,6 +128,7 @@ private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e)
case WorkspaceChangeKind.AnalyzerConfigDocumentChanged:
case WorkspaceChangeKind.AnalyzerConfigDocumentReloaded:
break;
default:
throw ExceptionUtilities.UnexpectedValue(e.Kind);
}
......@@ -159,8 +148,7 @@ internal void OnSolutionBuildCompleted()
var inProgressState = ClearInProgressState();
// enqueue build/live sync in the queue.
var asyncToken = _listener.BeginAsyncOperation("OnSolutionBuild");
_taskQueue.ScheduleTask(async () =>
_taskQueue.ScheduleTask("OnSolutionBuild", async () =>
{
// nothing to do
if (inProgressState == null)
......@@ -187,7 +175,7 @@ internal void OnSolutionBuildCompleted()
}
inProgressState.Done();
}).CompletesAsyncOperation(asyncToken);
}, CancellationToken.None);
}
private Task CleanupAllLiveErrorsAsync(DiagnosticAnalyzerService diagnosticService, IEnumerable<ProjectId> projects)
......@@ -264,11 +252,7 @@ public void AddNewErrors(ProjectId projectId, DiagnosticData diagnostic)
// capture state that will be processed in background thread.
var state = GetOrCreateInProgressState();
var asyncToken = _listener.BeginAsyncOperation("Project New Errors");
_taskQueue.ScheduleTask(() =>
{
state.AddError(projectId, diagnostic);
}).CompletesAsyncOperation(asyncToken);
_taskQueue.ScheduleTask("Project New Errors", () => state.AddError(projectId, diagnostic), CancellationToken.None);
}
public void AddNewErrors(DocumentId documentId, DiagnosticData diagnostic)
......@@ -276,11 +260,7 @@ public void AddNewErrors(DocumentId documentId, DiagnosticData diagnostic)
// capture state that will be processed in background thread.
var state = GetOrCreateInProgressState();
var asyncToken = _listener.BeginAsyncOperation("Document New Errors");
_taskQueue.ScheduleTask(() =>
{
state.AddError(documentId, diagnostic);
}).CompletesAsyncOperation(asyncToken);
_taskQueue.ScheduleTask("Document New Errors", () => state.AddError(documentId, diagnostic), CancellationToken.None);
}
public void AddNewErrors(
......@@ -289,8 +269,7 @@ public void AddNewErrors(DocumentId documentId, DiagnosticData diagnostic)
// capture state that will be processed in background thread
var state = GetOrCreateInProgressState();
var asyncToken = _listener.BeginAsyncOperation("Project New Errors");
_taskQueue.ScheduleTask(() =>
_taskQueue.ScheduleTask("Project New Errors", () =>
{
foreach (var kv in documentErrorMap)
{
......@@ -298,7 +277,7 @@ public void AddNewErrors(DocumentId documentId, DiagnosticData diagnostic)
}
state.AddErrors(projectId, projectErrors);
}).CompletesAsyncOperation(asyncToken);
}, CancellationToken.None);
}
private InProgressState BuildInprogressState
......
......@@ -5,37 +5,27 @@
using System;
using System.Collections.Generic;
using System.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.Implementation.Workspaces;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.VisualStudio.Threading;
using Roslyn.Utilities;
namespace Microsoft.VisualStudio.LanguageServices.Implementation
{
[ExportWorkspaceService(typeof(IWorkspaceTaskSchedulerFactory), ServiceLayer.Host), Shared]
internal class VisualStudioTaskSchedulerFactory : EditorTaskSchedulerFactory
[ExportWorkspaceService(typeof(ITaskSchedulerProvider), ServiceLayer.Host), Shared]
internal sealed class VisualStudioTaskSchedulerProvider : ITaskSchedulerProvider
{
private readonly IThreadingContext _threadingContext;
public TaskScheduler CurrentContextScheduler { get; }
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public VisualStudioTaskSchedulerFactory(IThreadingContext threadingContext, IAsynchronousOperationListenerProvider listenerProvider)
: base(listenerProvider)
public VisualStudioTaskSchedulerProvider(IThreadingContext threadingContext)
{
_threadingContext = threadingContext;
CurrentContextScheduler = new JoinableTaskFactoryTaskScheduler(threadingContext.JoinableTaskFactory);
}
public override IWorkspaceTaskScheduler CreateEventingTaskQueue()
{
return new WorkspaceTaskQueue(this, new JoinableTaskFactoryTaskScheduler(_threadingContext.JoinableTaskFactory));
}
private class JoinableTaskFactoryTaskScheduler : TaskScheduler
private sealed class JoinableTaskFactoryTaskScheduler : TaskScheduler
{
private readonly JoinableTaskFactory _joinableTaskFactory;
......
......@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Roslyn.Utilities;
......@@ -20,14 +21,12 @@ internal class GlobalOperationNotificationService : AbstractGlobalOperationNotif
private readonly HashSet<GlobalOperationRegistration> _registrations = new HashSet<GlobalOperationRegistration>();
private readonly HashSet<string> _operations = new HashSet<string>();
private readonly SimpleTaskQueue _eventQueue = new SimpleTaskQueue(TaskScheduler.Default);
private readonly TaskQueue _eventQueue;
private readonly EventMap _eventMap = new EventMap();
private readonly IAsynchronousOperationListener _listener;
public GlobalOperationNotificationService(IAsynchronousOperationListener listener)
{
_listener = listener;
_eventQueue = new TaskQueue(listener, TaskScheduler.Default);
}
public override GlobalOperationRegistration Start(string operation)
......@@ -52,33 +51,24 @@ public override GlobalOperationRegistration Start(string operation)
}
}
protected virtual Task RaiseGlobalOperationStartedAsync()
private Task RaiseGlobalOperationStartedAsync()
{
var ev = _eventMap.GetEventHandlers<EventHandler>(GlobalOperationStartedEventName);
if (ev.HasHandlers)
{
var asyncToken = _listener.BeginAsyncOperation("GlobalOperationStarted");
return _eventQueue.ScheduleTask(() =>
{
ev.RaiseEvent(handler => handler(this, EventArgs.Empty));
}).CompletesAsyncOperation(asyncToken);
return _eventQueue.ScheduleTask(GlobalOperationStartedEventName, () => ev.RaiseEvent(handler => handler(this, EventArgs.Empty)), CancellationToken.None);
}
return Task.CompletedTask;
}
protected virtual Task RaiseGlobalOperationStoppedAsync(IReadOnlyList<string> operations, bool cancelled)
private Task RaiseGlobalOperationStoppedAsync(IReadOnlyList<string> operations, bool cancelled)
{
var ev = _eventMap.GetEventHandlers<EventHandler<GlobalOperationEventArgs>>(GlobalOperationStoppedEventName);
if (ev.HasHandlers)
{
var asyncToken = _listener.BeginAsyncOperation("GlobalOperationStopped");
var args = new GlobalOperationEventArgs(operations, cancelled);
return _eventQueue.ScheduleTask(() =>
{
ev.RaiseEvent(handler => handler(this, args));
}).CompletesAsyncOperation(asyncToken);
return _eventQueue.ScheduleTask(GlobalOperationStoppedEventName, () => ev.RaiseEvent(handler => handler(this, args)), CancellationToken.None);
}
return Task.CompletedTask;
......
......@@ -13,6 +13,7 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Options
......@@ -36,14 +37,14 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
/// <summary>
/// Wraps an underlying <see cref="IGlobalOptionService"/> and exposes its data to workspace
/// clients. Also takes the <see cref="IGlobalOptionService.OptionChanged"/> notifications
/// and forwards them along using the same <see cref="IWorkspaceTaskScheduler"/> used by the
/// and forwards them along using the same <see cref="TaskQueue"/> used by the
/// <see cref="Workspace"/> this is connected to. i.e. instead of synchronously just passing
/// along the underlying events, these will be enqueued onto the workspace's eventing queue.
/// </summary>
internal class OptionService : IWorkspaceOptionService
internal sealed class OptionService : IWorkspaceOptionService
{
private readonly IGlobalOptionService _globalOptionService;
private readonly IWorkspaceTaskScheduler _taskQueue;
private readonly TaskQueue _taskQueue;
/// <summary>
/// Gate guarding <see cref="_eventHandlers"/> and <see cref="_documentOptionsProviders"/>.
......@@ -62,8 +63,9 @@ internal class OptionService : IWorkspaceOptionService
{
_globalOptionService = globalOptionService;
var workspaceTaskSchedulerFactory = workspaceServices.GetRequiredService<IWorkspaceTaskSchedulerFactory>();
_taskQueue = workspaceTaskSchedulerFactory.CreateEventingTaskQueue();
var schedulerProvider = workspaceServices.GetRequiredService<ITaskSchedulerProvider>();
var listenerProvider = workspaceServices.GetRequiredService<IWorkspaceAsynchronousOperationListenerProvider>();
_taskQueue = new TaskQueue(listenerProvider.GetListener(), schedulerProvider.CurrentContextScheduler);
_globalOptionService.OptionChanged += OnGlobalOptionServiceOptionChanged;
}
......@@ -77,7 +79,7 @@ public void OnWorkspaceDisposed(Workspace workspace)
private void OnGlobalOptionServiceOptionChanged(object? sender, OptionChangedEventArgs e)
{
_taskQueue.ScheduleTask(() =>
_taskQueue.ScheduleTask(nameof(OptionService) + "." + nameof(OnGlobalOptionServiceOptionChanged), () =>
{
// Ensure we grab the event handlers inside the scheduled task to prevent a race of people unsubscribing
// but getting the event later on the UI thread
......@@ -86,7 +88,7 @@ private void OnGlobalOptionServiceOptionChanged(object? sender, OptionChangedEve
{
handler(this, e);
}
}, "OptionsService.OnGlobalOptionServiceOptionChanged");
}, CancellationToken.None);
}
private ImmutableArray<EventHandler<OptionChangedEventArgs>> GetEventHandlers()
......
......@@ -232,7 +232,7 @@ private IEnumerable<IAsynchronousOperationWaiter> GetCandidateWaiters(string[] f
return _singletonListeners.Where(kv => featureNames.Contains(kv.Key)).Select(kv => (IAsynchronousOperationWaiter)kv.Value);
}
private class NullOperationListener : IAsynchronousOperationListener
private sealed class NullOperationListener : IAsynchronousOperationListener
{
public IAsyncToken BeginAsyncOperation(
string name,
......@@ -247,7 +247,7 @@ public async Task<bool> Delay(TimeSpan delay, CancellationToken cancellationToke
}
}
private class NullListenerProvider : IAsynchronousOperationListenerProvider
private sealed class NullListenerProvider : IAsynchronousOperationListenerProvider
{
public IAsynchronousOperationListener GetListener(string featureName) => NullListener;
}
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable enable
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Shared.TestHooks;
namespace Roslyn.Utilities
{
/// <summary>
/// Implements a queue of asynchronously executed tasks.
/// </summary>
internal sealed class TaskQueue
{
public IAsynchronousOperationListener Listener { get; }
public TaskScheduler Scheduler { get; }
private readonly object _gate = new object();
private Task _latestTask;
public TaskQueue(IAsynchronousOperationListener operationListener, TaskScheduler taskScheduler)
{
Contract.ThrowIfNull(operationListener);
Contract.ThrowIfNull(taskScheduler);
Listener = operationListener;
Scheduler = taskScheduler;
_latestTask = Task.CompletedTask;
}
public Task LastScheduledTask => _latestTask;
private IAsyncToken BeginOperation(string taskName)
=> Listener.BeginAsyncOperation(taskName);
private TTask EndOperation<TTask>(IAsyncToken token, TTask task) where TTask : Task
{
// send the notification on operation being complete but do not wait for the notification to be delivered
_ = task.CompletesAsyncOperation(token);
return task;
}
#pragma warning disable VSTHRD200 // Use "Async" suffix for async methods (Task wrappers, not asynchronous methods)
#pragma warning disable CS0618 // Type or member is obsolete (https://github.com/dotnet/roslyn/issues/42742)
/// <summary>
/// Enqueue specified <paramref name="operation"/> and notify <see cref="Listener"/> of its start and completion.
/// </summary>
/// <returns>The <see cref="Task"/> that executes the operation.</returns>
public Task ScheduleTask(string taskName, Action operation, CancellationToken cancellationToken)
=> EndOperation(BeginOperation(taskName), ScheduleTaskInProgress(operation, cancellationToken));
/// <inheritdoc cref="ScheduleTask(string, Action, CancellationToken)"/>
public Task<T> ScheduleTask<T>(string taskName, Func<T> operation, CancellationToken cancellationToken)
=> EndOperation(BeginOperation(taskName), ScheduleTaskInProgress(operation, cancellationToken));
/// <inheritdoc cref="ScheduleTask(string, Action, CancellationToken)"/>
public Task ScheduleTask(string taskName, Func<Task> operation, CancellationToken cancellationToken)
=> EndOperation(BeginOperation(taskName), ScheduleTaskInProgress(operation, cancellationToken));
/// <inheritdoc cref="ScheduleTask(string, Action, CancellationToken)"/>
public Task<T> ScheduleTask<T>(string taskName, Func<Task<T>> operation, CancellationToken cancellationToken)
=> EndOperation(BeginOperation(taskName), ScheduleTaskInProgress(operation, cancellationToken));
/// <summary>
/// Enqueue specified <paramref name="operation"/>.
/// Assumes <see cref="Listener"/> has already been notified of its start and will be notified when it completes.
/// </summary>
/// <returns>The <see cref="Task"/> that executes the operation.</returns>
[PerformanceSensitive("https://developercommunity.visualstudio.com/content/problem/854696/changing-target-framework-takes-10-minutes-with-10.html", AllowCaptures = false)]
[Obsolete("Should be private: https://github.com/dotnet/roslyn/issues/42742")]
public Task ScheduleTaskInProgress(Action operation, CancellationToken cancellationToken)
{
lock (_gate)
{
var task = _latestTask.SafeContinueWith(_ => operation(), cancellationToken, TaskContinuationOptions.None, Scheduler);
_latestTask = task;
return task;
}
}
/// <inheritdoc cref="ScheduleTaskInProgress(Action, CancellationToken)"/>
[PerformanceSensitive("https://developercommunity.visualstudio.com/content/problem/854696/changing-target-framework-takes-10-minutes-with-10.html", AllowCaptures = false)]
[Obsolete("Should be private: https://github.com/dotnet/roslyn/issues/42742")]
public Task<T> ScheduleTaskInProgress<T>(Func<T> operation, CancellationToken cancellationToken)
{
lock (_gate)
{
var task = _latestTask.SafeContinueWith(_ => operation(), cancellationToken, TaskContinuationOptions.None, Scheduler);
_latestTask = task;
return task;
}
}
/// <inheritdoc cref="ScheduleTaskInProgress(Action, CancellationToken)"/>
[PerformanceSensitive("https://developercommunity.visualstudio.com/content/problem/854696/changing-target-framework-takes-10-minutes-with-10.html", AllowCaptures = false)]
[Obsolete("Should be private: https://github.com/dotnet/roslyn/issues/42742")]
public Task ScheduleTaskInProgress(Func<Task> operation, CancellationToken cancellationToken)
{
lock (_gate)
{
var task = _latestTask.SafeContinueWithFromAsync(_ => operation(), cancellationToken, TaskContinuationOptions.None, Scheduler);
_latestTask = task;
return task;
}
}
/// <inheritdoc cref="ScheduleTaskInProgress(Action, CancellationToken)"/>
[PerformanceSensitive("https://developercommunity.visualstudio.com/content/problem/854696/changing-target-framework-takes-10-minutes-with-10.html", AllowCaptures = false)]
[Obsolete("Should be private: https://github.com/dotnet/roslyn/issues/42742")]
public Task<T> ScheduleTaskInProgress<T>(Func<Task<T>> operation, CancellationToken cancellationToken)
{
lock (_gate)
{
var task = _latestTask.SafeContinueWithFromAsync(_ => operation(), cancellationToken, TaskContinuationOptions.None, Scheduler);
_latestTask = task;
return task;
}
}
#pragma warning restore
}
}
......@@ -2,6 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable enable
using System.Threading.Tasks;
namespace Microsoft.CodeAnalysis.Host
......@@ -9,17 +11,8 @@ namespace Microsoft.CodeAnalysis.Host
/// <summary>
/// A factory that creates either sequential or parallel task schedulers.
/// </summary>
internal interface IWorkspaceTaskSchedulerFactory : IWorkspaceService
internal interface ITaskSchedulerProvider : IWorkspaceService
{
/// <summary>
/// Creates a workspace task scheduler that schedules tasks to run in parallel on the background.
/// </summary>
IWorkspaceTaskScheduler CreateBackgroundTaskScheduler();
/// <summary>
/// Creates a workspace task scheduler that schedules task to run in sequence to be used for raising
/// workspace events.
/// </summary>
IWorkspaceTaskScheduler CreateEventingTaskQueue();
TaskScheduler CurrentContextScheduler { get; }
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable enable
using Microsoft.CodeAnalysis.Shared.TestHooks;
namespace Microsoft.CodeAnalysis.Host
{
/// <summary>
/// Workspace service that provides <see cref="IAsynchronousOperationListener"/> instance.
/// </summary>
internal interface IWorkspaceAsynchronousOperationListenerProvider : IWorkspaceService
{
IAsynchronousOperationListener GetListener();
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.CodeAnalysis.Host
{
/// <summary>
/// An abstraction for running tasks either in sequence or in parallel.
/// </summary>
internal interface IWorkspaceTaskScheduler
{
/// <summary>
/// Execute the task action on a thread owned by a task scheduler.
/// </summary>
[SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods", Justification = "This is a Task wrapper, not an asynchronous method.")]
Task ScheduleTask(Action taskAction, string taskName, CancellationToken cancellationToken = default);
/// <summary>
/// Execute the task function on a thread owned by a task scheduler and return the schedule
/// task that can be used to wait for the result.
/// </summary>
[SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods", Justification = "This is a Task wrapper, not an asynchronous method.")]
Task<T> ScheduleTask<T>(Func<T> taskFunc, string taskName, CancellationToken cancellationToken = default);
/// <summary>
/// Execute the task function on a thread owned by a task scheduler and return the schedule
/// task that can be used to wait for the result.
/// </summary>
[SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods", Justification = "This is a Task wrapper, not an asynchronous method.")]
Task ScheduleTask(Func<Task> taskFunc, string taskName, CancellationToken cancellationToken = default);
/// <summary>
/// Execute the task function on a thread owned by a task scheduler and return the schedule
/// task that can be used to wait for the result.
/// </summary>
[SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods", Justification = "This is a Task wrapper, not an asynchronous method.")]
Task<T> ScheduleTask<T>(Func<Task<T>> taskFunc, string taskName, CancellationToken cancellationToken = default);
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable enable
using Microsoft.CodeAnalysis.Host.Mef;
using System;
using System.Composition;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.CodeAnalysis.Host
{
[ExportWorkspaceService(typeof(ITaskSchedulerProvider), ServiceLayer.Default)]
[Shared]
internal sealed class TaskSchedulerProvider : ITaskSchedulerProvider
{
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public TaskSchedulerProvider()
{
}
public TaskScheduler CurrentContextScheduler
=> (SynchronizationContext.Current != null) ? TaskScheduler.FromCurrentSynchronizationContext() : TaskScheduler.Default;
}
}
......@@ -2,35 +2,29 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Composition;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;
#nullable enable
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using System;
using System.Composition;
namespace Microsoft.CodeAnalysis.Editor.Implementation.Workspaces
namespace Microsoft.CodeAnalysis.Host
{
[ExportWorkspaceService(typeof(IWorkspaceTaskSchedulerFactory), ServiceLayer.Editor), Shared]
internal class EditorTaskSchedulerFactory : WorkspaceTaskSchedulerFactory
[ExportWorkspaceService(typeof(IWorkspaceAsynchronousOperationListenerProvider), ServiceLayer.Default)]
[Shared]
internal sealed class WorkspaceAsynchronousOperationListenerProvider : IWorkspaceAsynchronousOperationListenerProvider
{
private readonly IAsynchronousOperationListener _listener;
[ImportingConstructor]
public EditorTaskSchedulerFactory(IAsynchronousOperationListenerProvider listenerProvider)
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public WorkspaceAsynchronousOperationListenerProvider(IAsynchronousOperationListenerProvider listenerProvider)
{
_listener = listenerProvider.GetListener(FeatureAttribute.Workspace);
}
protected override object BeginAsyncOperation(string taskName)
{
return _listener.BeginAsyncOperation(taskName);
}
protected override void CompleteAsyncOperation(object asyncToken, Task task)
{
task.CompletesAsyncOperation((IAsyncToken)asyncToken);
}
public IAsynchronousOperationListener GetListener()
=> _listener;
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading;
using System.Threading.Tasks;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Host
{
internal partial class WorkspaceTaskSchedulerFactory
{
protected sealed class WorkspaceTaskQueue : IWorkspaceTaskScheduler
{
private readonly WorkspaceTaskSchedulerFactory _factory;
private readonly SimpleTaskQueue _queue;
public WorkspaceTaskQueue(WorkspaceTaskSchedulerFactory factory, TaskScheduler taskScheduler)
{
_factory = factory;
_queue = new SimpleTaskQueue(taskScheduler);
}
private T3 ScheduleTask<T1, T2, T3>(Func<T1, T2, T3> taskScheduler, string taskName, T1 arg1, T2 arg2) where T3 : Task
{
taskName ??= GetType().Name + ".Task";
var asyncToken = _factory.BeginAsyncOperation(taskName);
var task = taskScheduler(arg1, arg2);
_factory.CompleteAsyncOperation(asyncToken, task);
return task;
}
public Task ScheduleTask(Action taskAction, string taskName, CancellationToken cancellationToken)
{
return ScheduleTask((t, c) => _queue.ScheduleTask(t, c), taskName, taskAction, cancellationToken);
}
public Task<T> ScheduleTask<T>(Func<T> taskFunc, string taskName, CancellationToken cancellationToken)
{
return ScheduleTask((t, c) => _queue.ScheduleTask(t, c), taskName, taskFunc, cancellationToken);
}
public Task ScheduleTask(Func<Task> taskFunc, string taskName, CancellationToken cancellationToken = default)
{
return ScheduleTask((t, c) => _queue.ScheduleTask(t, c), taskName, taskFunc, cancellationToken);
}
public Task<T> ScheduleTask<T>(Func<Task<T>> taskFunc, string taskName, CancellationToken cancellationToken = default)
{
return ScheduleTask((t, c) => _queue.ScheduleTask(t, c), taskName, taskFunc, cancellationToken);
}
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading;
using System.Threading.Tasks;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Host
{
internal partial class WorkspaceTaskSchedulerFactory
{
private class WorkspaceTaskScheduler : IWorkspaceTaskScheduler
{
private readonly WorkspaceTaskSchedulerFactory _factory;
private readonly TaskScheduler _taskScheduler;
public WorkspaceTaskScheduler(WorkspaceTaskSchedulerFactory factory, TaskScheduler taskScheduler)
{
_factory = factory;
_taskScheduler = taskScheduler;
}
private TTask ScheduleTaskWorker<TTask>(
string taskName, Func<TTask> taskCreator)
where TTask : Task
{
taskName ??= GetType().Name + ".ScheduleTask";
var asyncToken = _factory.BeginAsyncOperation(taskName);
var task = taskCreator();
_factory.CompleteAsyncOperation(asyncToken, task);
return task;
}
public Task ScheduleTask(Action taskAction, string taskName, CancellationToken cancellationToken)
{
return ScheduleTaskWorker(
taskName, () => Task.Factory.SafeStartNew(taskAction, cancellationToken, _taskScheduler));
}
public Task<T> ScheduleTask<T>(Func<T> taskFunc, string taskName, CancellationToken cancellationToken)
{
return ScheduleTaskWorker(
taskName, () => Task.Factory.SafeStartNew(taskFunc, cancellationToken, _taskScheduler));
}
public Task ScheduleTask(Func<Task> taskFunc, string taskName, CancellationToken cancellationToken = default)
{
return ScheduleTaskWorker(
taskName, () => Task.Factory.SafeStartNewFromAsync(taskFunc, cancellationToken, _taskScheduler));
}
public Task<T> ScheduleTask<T>(Func<Task<T>> taskFunc, string taskName, CancellationToken cancellationToken = default)
{
return ScheduleTaskWorker(
taskName, () => Task.Factory.SafeStartNewFromAsync(taskFunc, cancellationToken, _taskScheduler));
}
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CodeAnalysis.Host.Mef;
using System.Composition;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.CodeAnalysis.Host
{
[ExportWorkspaceService(typeof(IWorkspaceTaskSchedulerFactory), ServiceLayer.Default)]
[Shared]
internal partial class WorkspaceTaskSchedulerFactory : IWorkspaceTaskSchedulerFactory
{
[ImportingConstructor]
public WorkspaceTaskSchedulerFactory()
{
}
public virtual IWorkspaceTaskScheduler CreateBackgroundTaskScheduler()
{
return new WorkspaceTaskScheduler(this, TaskScheduler.Default);
}
public virtual IWorkspaceTaskScheduler CreateEventingTaskQueue()
{
var taskScheduler = (SynchronizationContext.Current != null)
? TaskScheduler.FromCurrentSynchronizationContext()
: TaskScheduler.Default;
return new WorkspaceTaskQueue(this, taskScheduler);
}
protected virtual object BeginAsyncOperation(string taskName)
{
// do nothing ... overridden by services layer
return null;
}
protected virtual void CompleteAsyncOperation(object asyncToken, Task task)
{
// do nothing ... overridden by services layer
}
}
}
......@@ -51,7 +51,7 @@ public abstract partial class Workspace : IDisposable
// Current solution.
private Solution _latestSolution;
private readonly IWorkspaceTaskScheduler _taskQueue;
private readonly TaskQueue _taskQueue;
// test hooks.
internal static bool TestHookStandaloneProjectsDoNotHoldReferences = false;
......@@ -83,8 +83,9 @@ protected Workspace(HostServices host, string? workspaceKind)
_optionService.RegisterWorkspace(this);
// queue used for sending events
var workspaceTaskSchedulerFactory = _services.GetRequiredService<IWorkspaceTaskSchedulerFactory>();
_taskQueue = workspaceTaskSchedulerFactory.CreateEventingTaskQueue();
var schedulerProvider = _services.GetRequiredService<ITaskSchedulerProvider>();
var listenerProvider = _services.GetRequiredService<IWorkspaceAsynchronousOperationListenerProvider>();
_taskQueue = new TaskQueue(listenerProvider.GetListener(), schedulerProvider.CurrentContextScheduler);
// initialize with empty solution
var info = SolutionInfo.Create(SolutionId.CreateNewId(), VersionStamp.Create());
......@@ -285,18 +286,18 @@ internal void UpdateCurrentSolutionOnOptionsChanged()
/// Executes an action as a background task, as part of a sequential queue of tasks.
/// </summary>
[SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods", Justification = "This is a Task wrapper, not an asynchronous method.")]
protected internal Task ScheduleTask(Action action, string taskName = "Workspace.Task")
protected internal Task ScheduleTask(Action action, string? taskName = "Workspace.Task")
{
return _taskQueue.ScheduleTask(action, taskName);
return _taskQueue.ScheduleTask(taskName ?? "Workspace.Task", action, CancellationToken.None);
}
/// <summary>
/// Execute a function as a background task, as part of a sequential queue of tasks.
/// </summary>
[SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods", Justification = "This is a Task wrapper, not an asynchronous method.")]
protected internal Task<T> ScheduleTask<T>(Func<T> func, string taskName = "Workspace.Task")
protected internal Task<T> ScheduleTask<T>(Func<T> func, string? taskName = "Workspace.Task")
{
return _taskQueue.ScheduleTask(func, taskName);
return _taskQueue.ScheduleTask(taskName ?? "Workspace.Task", func, CancellationToken.None);
}
/// <summary>
......
......@@ -401,7 +401,6 @@
<Compile Include="$(MSBuildThisFileDirectory)Utilities\RestrictedInternalsVisibleToAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Utilities\SemaphoreSlimFactory.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Utilities\SerializableBytes.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Utilities\SimpleTaskQueue.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Utilities\SoftCrashException.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Utilities\SpecializedTasks.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Utilities\StringBreaker.cs" />
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
namespace Roslyn.Utilities
{
/// <summary>
/// schedules task to run in sequence.
/// </summary>
internal sealed class SimpleTaskQueue
{
private readonly TaskScheduler _taskScheduler;
/// <summary>
/// An object to synchronize reads/writes of all mutable fields of this class.
/// </summary>
private readonly object _gate = new object();
private Task _latestTask;
public SimpleTaskQueue(TaskScheduler taskScheduler)
{
_taskScheduler = taskScheduler;
_latestTask = Task.CompletedTask;
}
[PerformanceSensitive(
"https://developercommunity.visualstudio.com/content/problem/854696/changing-target-framework-takes-10-minutes-with-10.html",
AllowCaptures = false)]
[SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods", Justification = "This is a Task wrapper, not an asynchronous method.")]
public Task ScheduleTask(Action taskAction, CancellationToken cancellationToken = default)
{
lock (_gate)
{
var task = _latestTask.SafeContinueWith(_ => taskAction(), cancellationToken, TaskContinuationOptions.None, _taskScheduler);
_latestTask = task;
return task;
}
}
[PerformanceSensitive(
"https://developercommunity.visualstudio.com/content/problem/854696/changing-target-framework-takes-10-minutes-with-10.html",
AllowCaptures = false)]
[SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods", Justification = "This is a Task wrapper, not an asynchronous method.")]
public Task<T> ScheduleTask<T>(Func<T> taskFunc, CancellationToken cancellationToken = default)
{
lock (_gate)
{
var task = _latestTask.SafeContinueWith(_ => taskFunc(), cancellationToken, TaskContinuationOptions.None, _taskScheduler);
_latestTask = task;
return task;
}
}
[PerformanceSensitive(
"https://developercommunity.visualstudio.com/content/problem/854696/changing-target-framework-takes-10-minutes-with-10.html",
AllowCaptures = false)]
[SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods", Justification = "This is a Task wrapper, not an asynchronous method.")]
public Task ScheduleTask(Func<Task> taskFuncAsync, CancellationToken cancellationToken = default)
{
lock (_gate)
{
var task = _latestTask.SafeContinueWithFromAsync(_ => taskFuncAsync(), cancellationToken, TaskContinuationOptions.None, _taskScheduler);
_latestTask = task;
return task;
}
}
[PerformanceSensitive(
"https://developercommunity.visualstudio.com/content/problem/854696/changing-target-framework-takes-10-minutes-with-10.html",
AllowCaptures = false)]
[SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods", Justification = "This is a Task wrapper, not an asynchronous method.")]
public Task<T> ScheduleTask<T>(Func<Task<T>> taskFuncAsync, CancellationToken cancellationToken = default)
{
lock (_gate)
{
var task = _latestTask.SafeContinueWithFromAsync(_ => taskFuncAsync(), cancellationToken, TaskContinuationOptions.None, _taskScheduler);
_latestTask = task;
return task;
}
}
public Task LastScheduledTask => _latestTask;
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册