Fix initialization contract for RazorProjectInfo drivers

`AbstractRazorProjectInfoDriver`can't call `InitializeAsync(...)` in its constructor because the driver will only be partially constructed. To address that, add a `StartInitialization` method that drivers call from their constructor. This will kick off initialization and set the result of a `TaskCompletionSource` when it finishes.
This commit is contained in:
Dustin Campbell 2024-06-13 10:36:01 -07:00
Родитель af6e0d8735
Коммит 8e085c4e66
3 изменённых файлов: 39 добавлений и 11 удалений

Просмотреть файл

@ -57,6 +57,8 @@ internal partial class FileWatcherBasedRazorProjectInfoDriver : AbstractRazorPro
_fileSystemWatcher?.Dispose();
_fileSystemWatcher = null;
});
StartInitialization();
}
protected override async Task InitializeAsync(CancellationToken cancellationToken)

Просмотреть файл

@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Razor.PooledObjects;
using Microsoft.AspNetCore.Razor.ProjectSystem;
using Microsoft.CodeAnalysis.Razor.Logging;
using Microsoft.CodeAnalysis.Razor.Utilities;
using Microsoft.VisualStudio.Threading;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem;
@ -28,7 +29,7 @@ internal abstract partial class AbstractRazorProjectInfoDriver : IRazorProjectIn
private readonly AsyncBatchingWorkQueue<Work> _workQueue;
private readonly Dictionary<ProjectKey, RazorProjectInfo> _latestProjectInfoMap;
private ImmutableArray<IRazorProjectInfoListener> _listeners;
private readonly Task _initializeTask;
private readonly TaskCompletionSource<bool> _initializationTaskSource;
protected CancellationToken DisposalToken => _disposeTokenSource.Token;
@ -40,7 +41,7 @@ internal abstract partial class AbstractRazorProjectInfoDriver : IRazorProjectIn
_workQueue = new AsyncBatchingWorkQueue<Work>(delay ?? DefaultDelay, ProcessBatchAsync, _disposeTokenSource.Token);
_latestProjectInfoMap = [];
_listeners = [];
_initializeTask = InitializeAsync(_disposeTokenSource.Token);
_initializationTaskSource = new();
}
public void Dispose()
@ -57,10 +58,29 @@ internal abstract partial class AbstractRazorProjectInfoDriver : IRazorProjectIn
public Task WaitForInitializationAsync()
{
#pragma warning disable VSTHRD003 // Avoid awaiting foreign Tasks
return _initializeTask;
return _initializationTaskSource.Task;
#pragma warning restore VSTHRD003 // Avoid awaiting foreign Tasks
}
/// <summary>
/// MUST be called in the constructor of any <see cref="AbstractRazorProjectInfoDriver"/> descendent
/// to kick off initialization.
/// </summary>
protected void StartInitialization()
{
// Kick off initialization asynchronously and call TrySetResult(true) in the continuation.
InitializeAsync(_disposeTokenSource.Token)
.ContinueWith(
_ =>
{
_initializationTaskSource.TrySetResult(true);
},
_disposeTokenSource.Token,
TaskContinuationOptions.OnlyOnRanToCompletion,
TaskScheduler.Default)
.Forget();
}
protected abstract Task InitializeAsync(CancellationToken cancellationToken);
private async ValueTask ProcessBatchAsync(ImmutableArray<Work> items, CancellationToken token)
@ -125,7 +145,7 @@ internal abstract partial class AbstractRazorProjectInfoDriver : IRazorProjectIn
public ImmutableArray<RazorProjectInfo> GetLatestProjectInfo()
{
if (!_initializeTask.IsCompleted)
if (!_initializationTaskSource.Task.IsCompleted)
{
throw new InvalidOperationException($"{nameof(GetLatestProjectInfo)} cannot be called before initialization is complete.");
}
@ -145,7 +165,7 @@ internal abstract partial class AbstractRazorProjectInfoDriver : IRazorProjectIn
public void AddListener(IRazorProjectInfoListener listener)
{
if (!_initializeTask.IsCompleted)
if (!_initializationTaskSource.Task.IsCompleted)
{
throw new InvalidOperationException($"An {nameof(IRazorProjectInfoListener)} cannot be added before initialization is complete.");
}

Просмотреть файл

@ -10,13 +10,19 @@ using Microsoft.CodeAnalysis.Razor.ProjectSystem;
namespace Microsoft.VisualStudio.Razor.LanguageClient.ProjectSystem;
internal sealed partial class RazorProjectInfoDriver(
IProjectSnapshotManager projectManager,
ILoggerFactory loggerFactory,
TimeSpan? delay = null)
: AbstractRazorProjectInfoDriver(loggerFactory, delay)
internal sealed partial class RazorProjectInfoDriver : AbstractRazorProjectInfoDriver
{
private readonly IProjectSnapshotManager _projectManager = projectManager;
private readonly IProjectSnapshotManager _projectManager;
public RazorProjectInfoDriver(
IProjectSnapshotManager projectManager,
ILoggerFactory loggerFactory,
TimeSpan? delay = null) : base(loggerFactory, delay)
{
_projectManager = projectManager;
StartInitialization();
}
protected override Task InitializeAsync(CancellationToken cancellationToken)
{