Skip to content

Commit e4f55a9

Browse files
authored
Merge pull request #9684 from lifengl/dev/lifengl/preventBlockingThreadPool
Prevent blocking thread pool during initialization.
2 parents 56ccd0a + 5b04bb2 commit e4f55a9

File tree

2 files changed

+55
-41
lines changed

2 files changed

+55
-41
lines changed

src/Microsoft.VisualStudio.ProjectSystem.Managed/GlobalSuppressions.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,3 @@
2424
[assembly: SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods", Justification = "By design", Scope = "member", Target = "~M:Microsoft.VisualStudio.Threading.TaskResult.EmptyEnumerable``1~System.Threading.Tasks.Task{System.Collections.Generic.IEnumerable{``0}}")]
2525
[assembly: SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods", Justification = "<Pending>", Scope = "member", Target = "~M:Microsoft.VisualStudio.ProjectSystem.Debug.LaunchSettingsProviderExtensions.WaitForFirstSnapshot(Microsoft.VisualStudio.ProjectSystem.Debug.ILaunchSettingsProvider,System.Threading.CancellationToken)~System.Threading.Tasks.Task{Microsoft.VisualStudio.ProjectSystem.Debug.ILaunchSettings}")]
2626

27-
[assembly: SuppressMessage("Usage", "VSTHRD102:Implement internal logic asynchronously", Justification = "<Pending>", Scope = "member", Target = "~M:Microsoft.VisualStudio.ProjectSystem.Debug.LaunchSettingsProvider.WatchLaunchSettingsFile")]

src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/Debug/LaunchSettingsProvider.cs

Lines changed: 55 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,8 @@ internal class LaunchSettingsProvider : ProjectValueDataSourceBase<ILaunchSettin
6161
private readonly AsyncLazy<string> _launchSettingsFilePath;
6262
private readonly SequentialTaskExecutor _sequentialTaskQueue;
6363
private readonly Lazy<LaunchProfile?> _defaultLaunchProfile;
64+
private readonly AsyncLazy<IFileWatcher?> _launchSettingFileWatcher;
6465

65-
private IFileWatcher? _launchSettingFileWatcher;
66-
private int _launchSettingFileWatcherCookie;
6766
private IReceivableSourceBlock<ILaunchSettings>? _changedSourceBlock;
6867
private IBroadcastBlock<ILaunchSettings>? _broadcastBlock;
6968
private IReceivableSourceBlock<IProjectVersionedValue<ILaunchSettings>>? _versionedChangedSourceBlock;
@@ -103,6 +102,10 @@ public LaunchSettingsProvider(
103102
_sequentialTaskQueue = new SequentialTaskExecutor(new JoinableTaskContextNode(joinableTaskContext), nameof(LaunchSettingsProvider));
104103

105104
_launchSettingsFilePath = new AsyncLazy<string>(GetLaunchSettingsFilePathNoCacheAsync, commonProjectServices.ThreadingService.JoinableTaskFactory);
105+
_launchSettingFileWatcher = new AsyncLazy<IFileWatcher?>(WatchLaunchSettingsFileAsync, commonProjectServices.ThreadingService.JoinableTaskFactory)
106+
{
107+
SuppressRecursiveFactoryDetection = true
108+
};
106109

107110
_defaultLaunchProfile = new Lazy<LaunchProfile?>(() =>
108111
{
@@ -217,8 +220,24 @@ protected override void Initialize()
217220
JoinUpstreamDataSources(_projectSubscriptionService.ProjectRuleSource, _commonProjectServices.Project.Capabilities);
218221
}
219222

220-
// Make sure we are watching the file at this point
221-
WatchLaunchSettingsFile();
223+
FileChangeScheduler?.Dispose();
224+
225+
Assumes.Present(_projectServices.ProjectAsynchronousTasks);
226+
227+
// Create our scheduler for processing file changes
228+
FileChangeScheduler = new TaskDelayScheduler(
229+
FileChangeProcessingDelay,
230+
_commonProjectServices.ThreadingService,
231+
_projectServices.ProjectAsynchronousTasks.UnloadCancellationToken);
232+
233+
234+
// establish the file watcher. We don't need wait this, because files can be changed in the system anyway, so blocking our process
235+
// doesn't provide real benefit. It is of course possible that the file is changed before the watcher is established. To eliminate this
236+
// gap, we can recheck file after the watcher is established. I will skip this for now.
237+
_project.Services.FaultHandler.Forget(
238+
_launchSettingFileWatcher.GetValueAsync(),
239+
_project,
240+
severity: ProjectFaultSeverity.LimitedFunctionality);
222241
}
223242

224243
/// <summary>
@@ -517,16 +536,23 @@ protected async Task HandleLaunchSettingsFileChangedAsync()
517536
{
518537
Assumes.NotNull(FileChangeScheduler);
519538

520-
await FileChangeScheduler.ScheduleAsyncTask(token =>
539+
try
521540
{
522-
if (token.IsCancellationRequested)
541+
await FileChangeScheduler.ScheduleAsyncTask(token =>
523542
{
524-
return Task.CompletedTask;
525-
}
543+
if (token.IsCancellationRequested)
544+
{
545+
return Task.CompletedTask;
546+
}
526547

527-
// Updates need to be sequenced
528-
return _sequentialTaskQueue.ExecuteTask(() => UpdateProfilesAsync(null));
529-
});
548+
// Updates need to be sequenced
549+
return _sequentialTaskQueue.ExecuteTask(() => UpdateProfilesAsync(null));
550+
});
551+
}
552+
catch (ObjectDisposedException)
553+
{
554+
// during closing the FileChangeScheduler can be disposed while the task to process the last file change is still running.
555+
}
530556
}
531557
}
532558

@@ -547,53 +573,42 @@ protected async Task EnsureSettingsFolderAsync()
547573
/// Sets up a file system watcher to look for changes to the launchsettings.json file. It watches at the root of the
548574
/// project otherwise we force the project to have a properties folder.
549575
/// </summary>
550-
private void WatchLaunchSettingsFile()
576+
private async Task<IFileWatcher?> WatchLaunchSettingsFileAsync()
551577
{
552-
if (_launchSettingFileWatcher is null)
553-
{
554-
FileChangeScheduler?.Dispose();
578+
Assumes.Present(_projectServices.ProjectAsynchronousTasks);
579+
CancellationToken cancellationToken = _projectServices.ProjectAsynchronousTasks.UnloadCancellationToken;
555580

556-
Assumes.Present(_projectServices.ProjectAsynchronousTasks);
581+
IFileWatcher? fileWatcher = null;
557582

558-
// Create our scheduler for processing file changes
559-
FileChangeScheduler = new TaskDelayScheduler(FileChangeProcessingDelay, _commonProjectServices.ThreadingService,
560-
_projectServices.ProjectAsynchronousTasks.UnloadCancellationToken);
583+
string launchSettingsToWatch = await GetLaunchSettingsFilePathAsync();
584+
if (launchSettingsToWatch is not null)
585+
{
586+
fileWatcher = await _fileWatcherService.CreateFileWatcherAsync(this, FileWatchChangeKinds.Changed | FileWatchChangeKinds.Added | FileWatchChangeKinds.Removed, cancellationToken);
561587

562588
try
563589
{
564-
JoinableFactory.Run(async () =>
565-
{
566-
string launchSettingsToWatch = await GetLaunchSettingsFilePathAsync();
567-
if (launchSettingsToWatch is not null)
568-
{
569-
_launchSettingFileWatcher = await _fileWatcherService.CreateFileWatcherAsync(this, FileWatchChangeKinds.Changed | FileWatchChangeKinds.Added | FileWatchChangeKinds.Removed, CancellationToken.None);
570-
_launchSettingFileWatcherCookie = await _launchSettingFileWatcher.RegisterFileAsync(launchSettingsToWatch, CancellationToken.None);
571-
}
572-
});
590+
// disposing the file watcher will unregister the file, so we don't have to track the cookie.
591+
_ = await fileWatcher.RegisterFileAsync(launchSettingsToWatch, cancellationToken);
573592
}
574593
catch (Exception ex) when (ex is IOException || ex is ArgumentException)
575594
{
576595
// If the project folder is no longer available this will throw, which can happen during branch switching
596+
await fileWatcher.DisposeAsync();
597+
fileWatcher = null;
577598
}
578599
}
600+
601+
return fileWatcher;
579602
}
580603

581604
protected override void Dispose(bool disposing)
582605
{
583606
if (disposing)
584607
{
585-
if (_launchSettingFileWatcher is not null)
586-
{
587-
if (_launchSettingFileWatcherCookie is not 0)
588-
{
589-
// Unregister the file watcher
590-
_launchSettingFileWatcher.Unregister(_launchSettingFileWatcherCookie);
591-
_launchSettingFileWatcherCookie = 0;
592-
}
593-
594-
_launchSettingFileWatcher.Dispose();
595-
_launchSettingFileWatcher = null;
596-
}
608+
_project.Services.FaultHandler.Forget(
609+
_launchSettingFileWatcher.DisposeValueAsync(),
610+
_project,
611+
severity: ProjectFaultSeverity.Recoverable);
597612

598613
if (FileChangeScheduler is not null)
599614
{

0 commit comments

Comments
 (0)