@@ -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,13 @@ 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+ // establish the file watcher. We don't need wait this, because files can be changed in the system anyway, so blocking our process
224+ // doesn't provide real benefit. It is of course possible that the file is changed before the watcher is established. To eliminate this
225+ // gap, we can recheck file after the watcher is established. I will skip this for now.
226+ _project . Services . FaultHandler . Forget (
227+ _launchSettingFileWatcher . GetValueAsync ( ) ,
228+ _project ,
229+ severity : ProjectFaultSeverity . LimitedFunctionality ) ;
222230 }
223231
224232 /// <summary>
@@ -547,53 +555,48 @@ protected async Task EnsureSettingsFolderAsync()
547555 /// Sets up a file system watcher to look for changes to the launchsettings.json file. It watches at the root of the
548556 /// project otherwise we force the project to have a properties folder.
549557 /// </summary>
550- private void WatchLaunchSettingsFile ( )
558+ private async Task < IFileWatcher ? > WatchLaunchSettingsFileAsync ( )
551559 {
552- if ( _launchSettingFileWatcher is null )
553- {
554- FileChangeScheduler ? . Dispose ( ) ;
560+ FileChangeScheduler ? . Dispose ( ) ;
561+
562+ Assumes . Present ( _projectServices . ProjectAsynchronousTasks ) ;
555563
556- Assumes . Present ( _projectServices . ProjectAsynchronousTasks ) ;
564+ CancellationToken cancellationToken = _projectServices . ProjectAsynchronousTasks . UnloadCancellationToken ;
557565
558- // Create our scheduler for processing file changes
559- FileChangeScheduler = new TaskDelayScheduler ( FileChangeProcessingDelay , _commonProjectServices . ThreadingService ,
560- _projectServices . ProjectAsynchronousTasks . UnloadCancellationToken ) ;
566+ // Create our scheduler for processing file changes
567+ FileChangeScheduler = new TaskDelayScheduler ( FileChangeProcessingDelay , _commonProjectServices . ThreadingService , cancellationToken ) ;
568+
569+ IFileWatcher ? fileWatcher = null ;
570+
571+ string launchSettingsToWatch = await GetLaunchSettingsFilePathAsync ( ) ;
572+ if ( launchSettingsToWatch is not null )
573+ {
574+ fileWatcher = await _fileWatcherService . CreateFileWatcherAsync ( this , FileWatchChangeKinds . Changed | FileWatchChangeKinds . Added | FileWatchChangeKinds . Removed , cancellationToken ) ;
561575
562576 try
563577 {
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- } ) ;
578+ // disposing the file watcher will unregister the file, so we don't have to track the cookie.
579+ _ = await fileWatcher . RegisterFileAsync ( launchSettingsToWatch , cancellationToken ) ;
573580 }
574581 catch ( Exception ex ) when ( ex is IOException || ex is ArgumentException )
575582 {
576583 // If the project folder is no longer available this will throw, which can happen during branch switching
584+ await fileWatcher . DisposeAsync ( ) ;
585+ fileWatcher = null ;
577586 }
578587 }
588+
589+ return fileWatcher ;
579590 }
580591
581592 protected override void Dispose ( bool disposing )
582593 {
583594 if ( disposing )
584595 {
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- }
596+ _project . Services . FaultHandler . Forget (
597+ _launchSettingFileWatcher . DisposeValueAsync ( ) ,
598+ _project ,
599+ severity : ProjectFaultSeverity . Recoverable ) ;
597600
598601 if ( FileChangeScheduler is not null )
599602 {
0 commit comments