diff --git a/src/EditorFeatures/Core.Wpf/Options/LegacyEditorConfigDocumentOptionsProviderFactory.cs b/src/EditorFeatures/Core.Wpf/Options/LegacyEditorConfigDocumentOptionsProviderFactory.cs index f31f5c060938d40ded99420b9761b796ab92f07c..1fdd2772779e5ed7ef5135b36681be0ae4838efe 100644 --- a/src/EditorFeatures/Core.Wpf/Options/LegacyEditorConfigDocumentOptionsProviderFactory.cs +++ b/src/EditorFeatures/Core.Wpf/Options/LegacyEditorConfigDocumentOptionsProviderFactory.cs @@ -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 that ensures we don't watch for a file synchronously to /// avoid deadlocks. /// - 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); } } } diff --git a/src/EditorFeatures/Core/Implementation/Workspaces/EditorTaskSchedulerFactory.cs b/src/EditorFeatures/Core/Implementation/Workspaces/EditorTaskSchedulerFactory.cs deleted file mode 100644 index d4374aa6f231096de4ef5aaf6088bd91c52190bb..0000000000000000000000000000000000000000 --- a/src/EditorFeatures/Core/Implementation/Workspaces/EditorTaskSchedulerFactory.cs +++ /dev/null @@ -1,36 +0,0 @@ -// 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.Collections.Generic; -using System.Composition; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Shared.TestHooks; - -namespace Microsoft.CodeAnalysis.Editor.Implementation.Workspaces -{ - [ExportWorkspaceService(typeof(IWorkspaceTaskSchedulerFactory), ServiceLayer.Editor), Shared] - internal class EditorTaskSchedulerFactory : WorkspaceTaskSchedulerFactory - { - private readonly IAsynchronousOperationListener _listener; - - [ImportingConstructor] - public EditorTaskSchedulerFactory(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); - } - } -} diff --git a/src/EditorFeatures/Test/Workspaces/ProjectCacheHostServiceFactoryTests.cs b/src/EditorFeatures/Test/Workspaces/ProjectCacheHostServiceFactoryTests.cs index 654f3adba42fc0b6735b032182f41edb013f1ae5..55e6854ea4a5d9bed2db6cc36cd154c682cf1ee8 100644 --- a/src/EditorFeatures/Test/Workspaces/ProjectCacheHostServiceFactoryTests.cs +++ b/src/EditorFeatures/Test/Workspaces/ProjectCacheHostServiceFactoryTests.cs @@ -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 FindLanguageServices() { - 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; } diff --git a/src/EditorFeatures/TestUtilities/MinimalTestExportProvider.cs b/src/EditorFeatures/TestUtilities/MinimalTestExportProvider.cs index 6aa1edf8ffb18f93381a8e549677713cd1c401f4..ec122043d2f77d00a18f4c6134d196029da31b79 100644 --- a/src/EditorFeatures/TestUtilities/MinimalTestExportProvider.cs +++ b/src/EditorFeatures/TestUtilities/MinimalTestExportProvider.cs @@ -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), diff --git a/src/EditorFeatures/TestUtilities/TestExportProvider.cs b/src/EditorFeatures/TestUtilities/TestExportProvider.cs index f722dd92e1edc9a62e9128264242660fad397921..b8e6bdb6d022240b5586b0952d6b2d422732a6ab 100644 --- a/src/EditorFeatures/TestUtilities/TestExportProvider.cs +++ b/src/EditorFeatures/TestUtilities/TestExportProvider.cs @@ -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), diff --git a/src/EditorFeatures/TestUtilities/Workspaces/TestForegroundNotificationService.cs b/src/EditorFeatures/TestUtilities/Workspaces/TestForegroundNotificationService.cs index 37a6e7f44e6ad86f503b3f6803b9a4cfc8ff064c..d55c8f8eafeecfbb100dcc58fd787a4b3ae4ef77 100644 --- a/src/EditorFeatures/TestUtilities/Workspaces/TestForegroundNotificationService.cs +++ b/src/EditorFeatures/TestUtilities/Workspaces/TestForegroundNotificationService.cs @@ -15,19 +15,20 @@ internal class TestForegroundNotificationService : IForegroundNotificationServic { private readonly object _gate = new object(); private readonly List _tasks = new List(); - private readonly SimpleTaskQueue _queue = new SimpleTaskQueue(TaskScheduler.Default); + private readonly TaskQueue _queue = new TaskQueue(AsynchronousOperationListenerProvider.NullListener, TaskScheduler.Default); public void RegisterNotification(Func 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 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 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 } } diff --git a/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerService.cs index 38320788d4e6a8d8e79fb0cbcbc84f383cf55591..f5ab4a1107627e920b6cbfc788ee2f74fd3f749f 100644 --- a/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerService.cs @@ -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 _map; + private readonly ConditionalWeakTable.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(); + _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 GetHostDiagnosticAnalyzerPackage(IHostDiagnosticAnalyzerPackageProvider? diagnosticAnalyzerProviderService) diff --git a/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerService_IncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerService_IncrementalAnalyzer.cs index 6c2efc4534882b124aec28cbee93622d4ce5eaf8..f319e35183aee4ae8b3a7615ea7621e317106b54 100644 --- a/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerService_IncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerService_IncrementalAnalyzer.cs @@ -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 _map; - private readonly ConditionalWeakTable.CreateValueCallback _createIncrementalAnalyzer; - - [SuppressMessage("RoslyDiagnosticsReliability", "RS0034:Exported parts should have [ImportingConstructor]", Justification = "Private constructor used for deterministic field initialization")] - private DiagnosticAnalyzerService() - { - _map = new ConditionalWeakTable(); - _createIncrementalAnalyzer = CreateIncrementalAnalyzerCallback; - } - public IIncrementalAnalyzer CreateIncrementalAnalyzer(Workspace workspace) { if (!workspace.Options.GetOption(ServiceComponentOnOffOptions.DiagnosticProvider)) diff --git a/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerService_UpdateSource.cs b/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerService_UpdateSource.cs index 210d88dc7af54cb997de55769f8d0b960c7069f1..443086062a4233c181747f8e6e8c8e9a89fb3c5c 100644 --- a/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerService_UpdateSource.cs +++ b/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerService_UpdateSource.cs @@ -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 DiagnosticsUpdated { add @@ -66,8 +46,7 @@ internal void RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs args) var ev = _eventMap.GetEventHandlers>(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> // 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, 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); } } diff --git a/src/Features/Core/Portable/Diagnostics/DiagnosticService.cs b/src/Features/Core/Portable/Diagnostics/DiagnosticService.cs index 45a5b9a6a4fcab08b9e5a3974370655b73410107..0bc72291d186b884dfc485e5b5bbc75aab2d4fc5 100644 --- a/src/Features/Core/Portable/Diagnostics/DiagnosticService.cs +++ b/src/Features/Core/Portable/Diagnostics/DiagnosticService.cs @@ -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>> _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>>(); @@ -71,8 +68,7 @@ private void RaiseDiagnosticsUpdated(IDiagnosticUpdateSource source, Diagnostics var ev = _eventMap.GetEventHandlers>(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>(DiagnosticsUpdatedEventName); - var eventToken = _listener.BeginAsyncOperation(DiagnosticsUpdatedEventName); - _eventQueue.ScheduleTask(() => + _eventQueue.ScheduleTask(DiagnosticsUpdatedEventName, () => { using var pooledObject = SharedPools.Default>().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) diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.cs index 2fa3065125d73d74ba5a2d2860a683a9ab485c4d..5cde1ebbe7d26e5f84656f231dfcbe25f528287c 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.cs @@ -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) diff --git a/src/Features/Core/Portable/Workspace/BackgroundCompiler.cs b/src/Features/Core/Portable/Workspace/BackgroundCompiler.cs index bc1cf74982e2051deb14e295ad0051d6fb70e892..c35617a8434985c8dff5c42685f6e7562ead61ca 100644 --- a/src/Features/Core/Portable/Workspace/BackgroundCompiler.cs +++ b/src/Features/Core/Portable/Workspace/BackgroundCompiler.cs @@ -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(); - _compilationScheduler = taskSchedulerFactory.CreateBackgroundTaskScheduler(); + var listenerProvider = workspace.Services.GetRequiredService(); + _taskQueue = new TaskQueue(listenerProvider.GetListener(), TaskScheduler.Default); _cancellationSource = new CancellationTokenSource(); _workspace.WorkspaceChanged += OnWorkspaceChanged; @@ -131,9 +131,9 @@ private void CancelBuild(bool releasePreviousCompilations) ISet allProjects) { var cancellationToken = _cancellationSource.Token; - return _compilationScheduler.ScheduleTask( - () => BuildCompilationsAsync(solution, initialProject, allProjects, cancellationToken), + return _taskQueue.ScheduleTask( "BackgroundCompiler.BuildCompilationsAsync", + () => BuildCompilationsAsync(solution, initialProject, allProjects, cancellationToken), cancellationToken); } diff --git a/src/Features/Core/Portable/Workspace/BackgroundParser.cs b/src/Features/Core/Portable/Workspace/BackgroundParser.cs index 81c039a2f3da1400f1fc98a414737b38e5e9e034..90cd9570356ca44a977e6576d914158dfcf62861 100644 --- a/src/Features/Core/Portable/Workspace/BackgroundParser.cs +++ b/src/Features/Core/Portable/Workspace/BackgroundParser.cs @@ -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. /// - 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(); - _taskScheduler = taskSchedulerFactory.CreateBackgroundTaskScheduler(); + var listenerProvider = workspace.Services.GetRequiredService(); + _taskQueue = new TaskQueue(listenerProvider.GetListener(), TaskScheduler.Default); _documentTrackingService = workspace.Services.GetService(); @@ -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. diff --git a/src/VisualStudio/Core/Def/Implementation/Remote/RemoteHostClientServiceFactory.SolutionChecksumUpdater.cs b/src/VisualStudio/Core/Def/Implementation/Remote/RemoteHostClientServiceFactory.SolutionChecksumUpdater.cs index 6e9b08cf7ee050f49232051772eecdcf721de309..7090fc6c6c65e7d16f324e46a9466935c2cbbe11 100644 --- a/src/VisualStudio/Core/Def/Implementation/Remote/RemoteHostClientServiceFactory.SolutionChecksumUpdater.cs +++ b/src/VisualStudio/Core/Def/Implementation/Remote/RemoteHostClientServiceFactory.SolutionChecksumUpdater.cs @@ -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); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/TaskList/ExternalErrorDiagnosticUpdateSource.cs b/src/VisualStudio/Core/Def/Implementation/TaskList/ExternalErrorDiagnosticUpdateSource.cs index 8f094332339fd2978b5b3c00787d13759a8a3b45..90df4d4276e12d3e19090cc7709d1b807ffc88b9 100644 --- a/src/VisualStudio/Core/Def/Implementation/TaskList/ExternalErrorDiagnosticUpdateSource.cs +++ b/src/VisualStudio/Core/Def/Implementation/TaskList/ExternalErrorDiagnosticUpdateSource.cs @@ -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 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 diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioTaskSchedulerFactory.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioTaskSchedulerProvider.cs similarity index 65% rename from src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioTaskSchedulerFactory.cs rename to src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioTaskSchedulerProvider.cs index a2a659f501d44fd62d976433a6f9f2a996fa0916..ae1013c37b732f0c3e92ec7bb7cbf193a4f5286d 100644 --- a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioTaskSchedulerFactory.cs +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioTaskSchedulerProvider.cs @@ -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; diff --git a/src/Workspaces/Core/Portable/Notification/GlobalOperationNotificationService.cs b/src/Workspaces/Core/Portable/Notification/GlobalOperationNotificationService.cs index 940d01efe6080417e0bfe83f33b4292087fa7c54..cf23397ea5c881b72e0be836f5b73aba56bd9fdb 100644 --- a/src/Workspaces/Core/Portable/Notification/GlobalOperationNotificationService.cs +++ b/src/Workspaces/Core/Portable/Notification/GlobalOperationNotificationService.cs @@ -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 _registrations = new HashSet(); private readonly HashSet _operations = new HashSet(); - 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(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 operations, bool cancelled) + private Task RaiseGlobalOperationStoppedAsync(IReadOnlyList operations, bool cancelled) { var ev = _eventMap.GetEventHandlers>(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; diff --git a/src/Workspaces/Core/Portable/Options/OptionServiceFactory.cs b/src/Workspaces/Core/Portable/Options/OptionServiceFactory.cs index 095ec6eff2842e7b781849920da8128f85aada84..5ea0632e260418fc9f727b088036b8e5fb6996fe 100644 --- a/src/Workspaces/Core/Portable/Options/OptionServiceFactory.cs +++ b/src/Workspaces/Core/Portable/Options/OptionServiceFactory.cs @@ -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) /// /// Wraps an underlying and exposes its data to workspace /// clients. Also takes the notifications - /// and forwards them along using the same used by the + /// and forwards them along using the same used by the /// 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. /// - internal class OptionService : IWorkspaceOptionService + internal sealed class OptionService : IWorkspaceOptionService { private readonly IGlobalOptionService _globalOptionService; - private readonly IWorkspaceTaskScheduler _taskQueue; + private readonly TaskQueue _taskQueue; /// /// Gate guarding and . @@ -62,8 +63,9 @@ internal class OptionService : IWorkspaceOptionService { _globalOptionService = globalOptionService; - var workspaceTaskSchedulerFactory = workspaceServices.GetRequiredService(); - _taskQueue = workspaceTaskSchedulerFactory.CreateEventingTaskQueue(); + var schedulerProvider = workspaceServices.GetRequiredService(); + var listenerProvider = workspaceServices.GetRequiredService(); + _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> GetEventHandlers() diff --git a/src/Workspaces/Core/Portable/Shared/TestHooks/IAsynchronousOperationListenerProvider.cs b/src/Workspaces/Core/Portable/Shared/TestHooks/IAsynchronousOperationListenerProvider.cs index 24e7c40d7bd91ba89dbc63f6ca0c9a8af3508c1f..1fadc0749dd61ce4a0530ca9e9dea58271a01a2d 100644 --- a/src/Workspaces/Core/Portable/Shared/TestHooks/IAsynchronousOperationListenerProvider.cs +++ b/src/Workspaces/Core/Portable/Shared/TestHooks/IAsynchronousOperationListenerProvider.cs @@ -232,7 +232,7 @@ private IEnumerable 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 Delay(TimeSpan delay, CancellationToken cancellationToke } } - private class NullListenerProvider : IAsynchronousOperationListenerProvider + private sealed class NullListenerProvider : IAsynchronousOperationListenerProvider { public IAsynchronousOperationListener GetListener(string featureName) => NullListener; } diff --git a/src/Workspaces/Core/Portable/Utilities/TaskQueue.cs b/src/Workspaces/Core/Portable/Utilities/TaskQueue.cs new file mode 100644 index 0000000000000000000000000000000000000000..965929a6b6c01230218b6f7b8d6925cdf4dc2031 --- /dev/null +++ b/src/Workspaces/Core/Portable/Utilities/TaskQueue.cs @@ -0,0 +1,128 @@ +// 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 +{ + /// + /// Implements a queue of asynchronously executed tasks. + /// + 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(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) + /// + /// Enqueue specified and notify of its start and completion. + /// + /// The that executes the operation. + public Task ScheduleTask(string taskName, Action operation, CancellationToken cancellationToken) + => EndOperation(BeginOperation(taskName), ScheduleTaskInProgress(operation, cancellationToken)); + + /// + public Task ScheduleTask(string taskName, Func operation, CancellationToken cancellationToken) + => EndOperation(BeginOperation(taskName), ScheduleTaskInProgress(operation, cancellationToken)); + + /// + public Task ScheduleTask(string taskName, Func operation, CancellationToken cancellationToken) + => EndOperation(BeginOperation(taskName), ScheduleTaskInProgress(operation, cancellationToken)); + + /// + public Task ScheduleTask(string taskName, Func> operation, CancellationToken cancellationToken) + => EndOperation(BeginOperation(taskName), ScheduleTaskInProgress(operation, cancellationToken)); + + /// + /// Enqueue specified . + /// Assumes has already been notified of its start and will be notified when it completes. + /// + /// The that executes the operation. + [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; + } + } + + /// + [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 operation, CancellationToken cancellationToken) + { + lock (_gate) + { + var task = _latestTask.SafeContinueWith(_ => operation(), cancellationToken, TaskContinuationOptions.None, Scheduler); + _latestTask = task; + return task; + } + } + + /// + [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 operation, CancellationToken cancellationToken) + { + lock (_gate) + { + var task = _latestTask.SafeContinueWithFromAsync(_ => operation(), cancellationToken, TaskContinuationOptions.None, Scheduler); + _latestTask = task; + return task; + } + } + + /// + [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> operation, CancellationToken cancellationToken) + { + lock (_gate) + { + var task = _latestTask.SafeContinueWithFromAsync(_ => operation(), cancellationToken, TaskContinuationOptions.None, Scheduler); + _latestTask = task; + return task; + } + } +#pragma warning restore + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TaskScheduler/ITaskSchedulerProvider.cs b/src/Workspaces/Core/Portable/Workspace/Host/TaskScheduler/ITaskSchedulerProvider.cs new file mode 100644 index 0000000000000000000000000000000000000000..e18d21b8f73b0cc4cb057861573c53044e92f170 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Host/TaskScheduler/ITaskSchedulerProvider.cs @@ -0,0 +1,18 @@ +// 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.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.Host +{ + /// + /// A factory that creates either sequential or parallel task schedulers. + /// + internal interface ITaskSchedulerProvider : IWorkspaceService + { + TaskScheduler CurrentContextScheduler { get; } + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TaskScheduler/IWorkspaceAsynchronousOperationListenerProvider.cs b/src/Workspaces/Core/Portable/Workspace/Host/TaskScheduler/IWorkspaceAsynchronousOperationListenerProvider.cs new file mode 100644 index 0000000000000000000000000000000000000000..38fbd9c6c81b833687bc567986dd762c3aaa6d42 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Host/TaskScheduler/IWorkspaceAsynchronousOperationListenerProvider.cs @@ -0,0 +1,18 @@ +// 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 +{ + /// + /// Workspace service that provides instance. + /// + internal interface IWorkspaceAsynchronousOperationListenerProvider : IWorkspaceService + { + IAsynchronousOperationListener GetListener(); + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TaskScheduler/IWorkspaceTaskScheduler.cs b/src/Workspaces/Core/Portable/Workspace/Host/TaskScheduler/IWorkspaceTaskScheduler.cs deleted file mode 100644 index f12e619e6f62057affee49f4aa2d8666266681b9..0000000000000000000000000000000000000000 --- a/src/Workspaces/Core/Portable/Workspace/Host/TaskScheduler/IWorkspaceTaskScheduler.cs +++ /dev/null @@ -1,44 +0,0 @@ -// 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 -{ - /// - /// An abstraction for running tasks either in sequence or in parallel. - /// - internal interface IWorkspaceTaskScheduler - { - /// - /// Execute the task action on a thread owned by a task scheduler. - /// - [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); - - /// - /// 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. - /// - [SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods", Justification = "This is a Task wrapper, not an asynchronous method.")] - Task ScheduleTask(Func taskFunc, string taskName, CancellationToken cancellationToken = default); - - /// - /// 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. - /// - [SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods", Justification = "This is a Task wrapper, not an asynchronous method.")] - Task ScheduleTask(Func taskFunc, string taskName, CancellationToken cancellationToken = default); - - /// - /// 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. - /// - [SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods", Justification = "This is a Task wrapper, not an asynchronous method.")] - Task ScheduleTask(Func> taskFunc, string taskName, CancellationToken cancellationToken = default); - } -} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TaskScheduler/IWorkspaceTaskSchedulerFactory.cs b/src/Workspaces/Core/Portable/Workspace/Host/TaskScheduler/IWorkspaceTaskSchedulerFactory.cs deleted file mode 100644 index b0087ee25df77c17dd033a758dc26cb3ae39c5a4..0000000000000000000000000000000000000000 --- a/src/Workspaces/Core/Portable/Workspace/Host/TaskScheduler/IWorkspaceTaskSchedulerFactory.cs +++ /dev/null @@ -1,25 +0,0 @@ -// 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.Threading.Tasks; - -namespace Microsoft.CodeAnalysis.Host -{ - /// - /// A factory that creates either sequential or parallel task schedulers. - /// - internal interface IWorkspaceTaskSchedulerFactory : IWorkspaceService - { - /// - /// Creates a workspace task scheduler that schedules tasks to run in parallel on the background. - /// - IWorkspaceTaskScheduler CreateBackgroundTaskScheduler(); - - /// - /// Creates a workspace task scheduler that schedules task to run in sequence to be used for raising - /// workspace events. - /// - IWorkspaceTaskScheduler CreateEventingTaskQueue(); - } -} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TaskScheduler/TaskSchedulerProvider.cs b/src/Workspaces/Core/Portable/Workspace/Host/TaskScheduler/TaskSchedulerProvider.cs new file mode 100644 index 0000000000000000000000000000000000000000..1c6562932eb476aee72d09f64e9f6aaac729b6ab --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Host/TaskScheduler/TaskSchedulerProvider.cs @@ -0,0 +1,28 @@ +// 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; + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TaskScheduler/WorkspaceAsynchronousOperationListenerProvider.cs b/src/Workspaces/Core/Portable/Workspace/Host/TaskScheduler/WorkspaceAsynchronousOperationListenerProvider.cs new file mode 100644 index 0000000000000000000000000000000000000000..aa296bf4e0554ca9a148df0bcacb7bbe0ce37f00 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Host/TaskScheduler/WorkspaceAsynchronousOperationListenerProvider.cs @@ -0,0 +1,30 @@ +// 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 Microsoft.CodeAnalysis.Shared.TestHooks; +using System; +using System.Composition; + +namespace Microsoft.CodeAnalysis.Host +{ + [ExportWorkspaceService(typeof(IWorkspaceAsynchronousOperationListenerProvider), ServiceLayer.Default)] + [Shared] + internal sealed class WorkspaceAsynchronousOperationListenerProvider : IWorkspaceAsynchronousOperationListenerProvider + { + private readonly IAsynchronousOperationListener _listener; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public WorkspaceAsynchronousOperationListenerProvider(IAsynchronousOperationListenerProvider listenerProvider) + { + _listener = listenerProvider.GetListener(FeatureAttribute.Workspace); + } + + public IAsynchronousOperationListener GetListener() + => _listener; + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TaskScheduler/WorkspaceTaskSchedulerFactory.WorkspaceTaskQueue.cs b/src/Workspaces/Core/Portable/Workspace/Host/TaskScheduler/WorkspaceTaskSchedulerFactory.WorkspaceTaskQueue.cs deleted file mode 100644 index 231e62af0159bc0c0557174bc0e7a8c7f9d5a39b..0000000000000000000000000000000000000000 --- a/src/Workspaces/Core/Portable/Workspace/Host/TaskScheduler/WorkspaceTaskSchedulerFactory.WorkspaceTaskQueue.cs +++ /dev/null @@ -1,57 +0,0 @@ -// 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(Func 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 ScheduleTask(Func taskFunc, string taskName, CancellationToken cancellationToken) - { - return ScheduleTask((t, c) => _queue.ScheduleTask(t, c), taskName, taskFunc, cancellationToken); - } - - public Task ScheduleTask(Func taskFunc, string taskName, CancellationToken cancellationToken = default) - { - return ScheduleTask((t, c) => _queue.ScheduleTask(t, c), taskName, taskFunc, cancellationToken); - } - - public Task ScheduleTask(Func> taskFunc, string taskName, CancellationToken cancellationToken = default) - { - return ScheduleTask((t, c) => _queue.ScheduleTask(t, c), taskName, taskFunc, cancellationToken); - } - } - } -} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TaskScheduler/WorkspaceTaskSchedulerFactory.WorkspaceTaskScheduler.cs b/src/Workspaces/Core/Portable/Workspace/Host/TaskScheduler/WorkspaceTaskSchedulerFactory.WorkspaceTaskScheduler.cs deleted file mode 100644 index 6f276d985b575ae253df1df1a428727d98c1cdc7..0000000000000000000000000000000000000000 --- a/src/Workspaces/Core/Portable/Workspace/Host/TaskScheduler/WorkspaceTaskSchedulerFactory.WorkspaceTaskScheduler.cs +++ /dev/null @@ -1,63 +0,0 @@ -// 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( - string taskName, Func 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 ScheduleTask(Func taskFunc, string taskName, CancellationToken cancellationToken) - { - return ScheduleTaskWorker( - taskName, () => Task.Factory.SafeStartNew(taskFunc, cancellationToken, _taskScheduler)); - } - - public Task ScheduleTask(Func taskFunc, string taskName, CancellationToken cancellationToken = default) - { - return ScheduleTaskWorker( - taskName, () => Task.Factory.SafeStartNewFromAsync(taskFunc, cancellationToken, _taskScheduler)); - } - - public Task ScheduleTask(Func> taskFunc, string taskName, CancellationToken cancellationToken = default) - { - return ScheduleTaskWorker( - taskName, () => Task.Factory.SafeStartNewFromAsync(taskFunc, cancellationToken, _taskScheduler)); - } - } - } -} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TaskScheduler/WorkspaceTaskSchedulerFactory.cs b/src/Workspaces/Core/Portable/Workspace/Host/TaskScheduler/WorkspaceTaskSchedulerFactory.cs deleted file mode 100644 index bc1631af0936094dab9f025b1346f5afd83da26d..0000000000000000000000000000000000000000 --- a/src/Workspaces/Core/Portable/Workspace/Host/TaskScheduler/WorkspaceTaskSchedulerFactory.cs +++ /dev/null @@ -1,46 +0,0 @@ -// 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 - } - } -} diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace.cs b/src/Workspaces/Core/Portable/Workspace/Workspace.cs index 39c0d553264c060cac9bafa1440ada5e3b28cdc9..b28ec9dab0a94b7fe16c7c13ee223f84cf4be308 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace.cs @@ -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(); - _taskQueue = workspaceTaskSchedulerFactory.CreateEventingTaskQueue(); + var schedulerProvider = _services.GetRequiredService(); + var listenerProvider = _services.GetRequiredService(); + _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. /// [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); } /// /// Execute a function as a background task, as part of a sequential queue of tasks. /// [SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods", Justification = "This is a Task wrapper, not an asynchronous method.")] - protected internal Task ScheduleTask(Func func, string taskName = "Workspace.Task") + protected internal Task ScheduleTask(Func func, string? taskName = "Workspace.Task") { - return _taskQueue.ScheduleTask(func, taskName); + return _taskQueue.ScheduleTask(taskName ?? "Workspace.Task", func, CancellationToken.None); } /// diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems index 620bf59187db331e57d4410c3209a4adeba467a0..75f7b2a499223b931aca5c7a0ab03e3344a72538 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems @@ -401,7 +401,6 @@ - diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SimpleTaskQueue.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SimpleTaskQueue.cs deleted file mode 100644 index 00aca0a1c2ebe625caf591a842211a2cd5210fdf..0000000000000000000000000000000000000000 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SimpleTaskQueue.cs +++ /dev/null @@ -1,90 +0,0 @@ -// 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 -{ - /// - /// schedules task to run in sequence. - /// - internal sealed class SimpleTaskQueue - { - private readonly TaskScheduler _taskScheduler; - - /// - /// An object to synchronize reads/writes of all mutable fields of this class. - /// - 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 ScheduleTask(Func 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 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 ScheduleTask(Func> taskFuncAsync, CancellationToken cancellationToken = default) - { - lock (_gate) - { - var task = _latestTask.SafeContinueWithFromAsync(_ => taskFuncAsync(), cancellationToken, TaskContinuationOptions.None, _taskScheduler); - _latestTask = task; - return task; - } - } - - public Task LastScheduledTask => _latestTask; - } -}