diff --git a/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/LanguageServices/ActiveWorkspaceProjectContextHost.cs b/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/LanguageServices/ActiveWorkspaceProjectContextHost.cs
new file mode 100644
index 00000000000..a052c2a04ff
--- /dev/null
+++ b/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/LanguageServices/ActiveWorkspaceProjectContextHost.cs
@@ -0,0 +1,61 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.ComponentModel.Composition;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.VisualStudio.ProjectSystem.LanguageServices
+{
+ ///
+ /// Provides an implementation of that delegates
+ /// onto the active configuration's .
+ ///
+ [Export(typeof(IActiveWorkspaceProjectContextHost))]
+ [AppliesTo(ProjectCapability.DotNetLanguageService)]
+ internal class ActiveWorkspaceProjectContextHost : IActiveWorkspaceProjectContextHost
+ {
+ private readonly ActiveConfiguredProject _activeHost;
+ private readonly ActiveConfiguredProject _activeConfiguredProject;
+
+ [ImportingConstructor]
+ public ActiveWorkspaceProjectContextHost(ActiveConfiguredProject activeHost, ActiveConfiguredProject activeConfiguredProject)
+ {
+ _activeHost = activeHost;
+ _activeConfiguredProject = activeConfiguredProject;
+ }
+
+ public Task PublishAsync(CancellationToken cancellationToken = default)
+ {
+ return _activeHost.Value.PublishAsync(cancellationToken);
+ }
+
+ public async Task OpenContextForWriteAsync(Func action)
+ {
+ while (true)
+ {
+ try
+ {
+ await _activeHost.Value.OpenContextForWriteAsync(action);
+ }
+ catch (ActiveProjectConfigurationChangedException)
+ { // Host was unloaded because configuration changed, retry on new config
+ }
+ }
+ }
+
+ public async Task OpenContextForWriteAsync(Func> action)
+ {
+ while (true)
+ {
+ try
+ {
+ return await _activeHost.Value.OpenContextForWriteAsync(action);
+ }
+ catch (ActiveProjectConfigurationChangedException)
+ { // Host was unloaded because configuration changed, retry on new config
+ }
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/LanguageServices/IWorkspaceProjectContextHost.cs b/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/LanguageServices/IWorkspaceProjectContextHost.cs
index 609c28cab00..02cf3fea916 100644
--- a/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/LanguageServices/IWorkspaceProjectContextHost.cs
+++ b/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/LanguageServices/IWorkspaceProjectContextHost.cs
@@ -29,7 +29,7 @@ internal interface IWorkspaceProjectContextHost
/// it will join the load when it starts.
///
Task PublishAsync(CancellationToken cancellationToken = default);
-
+
///
/// Opens the , passing it to the specified action for writing.
///
@@ -42,6 +42,10 @@ internal interface IWorkspaceProjectContextHost
///
/// The result is awaited and the is unloaded.
///
+ ///
+ /// The represents the active one, and
+ /// the configuration changed.
+ ///
Task OpenContextForWriteAsync(Func action);
///
@@ -59,6 +63,10 @@ internal interface IWorkspaceProjectContextHost
///
/// The result is awaited and the is unloaded.
///
+ ///
+ /// The represents the active one, and
+ /// the configuration changed.
+ ///
Task OpenContextForWriteAsync(Func> action);
}
}
diff --git a/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/LanguageServices/WorkspaceContextHost.WorkspaceContextHostInstance.cs b/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/LanguageServices/WorkspaceContextHost.WorkspaceContextHostInstance.cs
index 5c949f50d4a..0db189c5c79 100644
--- a/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/LanguageServices/WorkspaceContextHost.WorkspaceContextHostInstance.cs
+++ b/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/LanguageServices/WorkspaceContextHost.WorkspaceContextHostInstance.cs
@@ -53,7 +53,7 @@ public Task InitializeAsync()
protected override async Task InitializeCoreAsync(CancellationToken cancellationToken)
{
- _contextAccessor = await _workspaceProjectContextProvider.CreateProjectContextAsync(_project);
+ _contextAccessor = await _workspaceProjectContextProvider.CreateProjectContextAsync(_project);
if (_contextAccessor == null)
return;
@@ -91,27 +91,38 @@ protected override async Task DisposeCoreUnderLockAsync(bool initialized)
public async Task OpenContextForWriteAsync(Func action)
{
- await WaitUntilInitializedCompletedAsync();
+ CheckForInitialized();
- await ExecuteUnderLockAsync(_ => action(_contextAccessor), _tasksService.UnloadCancellationToken);
+ try
+ {
+ await ExecuteUnderLockAsync(_ => action(_contextAccessor), _tasksService.UnloadCancellationToken);
+ }
+ catch (OperationCanceledException ex) when (ex.CancellationToken == DisposalToken)
+ { // We treat cancellation because our instance was disposed differently from when the project is unloading.
+ //
+ // The former indicates that the active configuration changed, and our ConfiguredProject is no longer
+ // considered implicitly "active", we throw a different exceptions to let callers handle that.
+ throw new ActiveProjectConfigurationChangedException();
+ }
}
public async Task OpenContextForWriteAsync(Func> action)
{
- await WaitUntilInitializedCompletedAsync();
+ CheckForInitialized();
- return await ExecuteUnderLockAsync(_ => action(_contextAccessor), _tasksService.UnloadCancellationToken);
+ try
+ {
+ return await ExecuteUnderLockAsync(_ => action(_contextAccessor), _tasksService.UnloadCancellationToken);
+ }
+ catch (OperationCanceledException ex) when (ex.CancellationToken == DisposalToken)
+ {
+ throw new ActiveProjectConfigurationChangedException();
+ }
}
internal async Task OnProjectChangedAsync(IProjectVersionedValue update, bool evaluation)
{
- CancellationToken cancellationToken = _tasksService.UnloadCancellationToken;
-
- await ExecuteUnderLockAsync(ct =>
- {
- return ApplyProjectChangesUnderLockAsync(update, evaluation, ct);
-
- }, cancellationToken);
+ await ExecuteUnderLockAsync(ct => ApplyProjectChangesUnderLockAsync(update, evaluation, ct), _tasksService.UnloadCancellationToken);
}
private Task ApplyProjectChangesUnderLockAsync(IProjectVersionedValue update, bool evaluation, CancellationToken cancellationToken)
@@ -139,9 +150,11 @@ private Task ApplyProjectChangesUnderLockAsync(IProjectVersionedValue service.
///
[Export(typeof(IImplicitlyActiveService))]
+ [Export(typeof(IWorkspaceProjectContextHost))]
[AppliesTo(ProjectCapability.DotNetLanguageService)]
internal partial class WorkspaceContextHost : AbstractMultiLifetimeComponent, IImplicitlyActiveService, IWorkspaceProjectContextHost
{
@@ -65,7 +66,7 @@ public async Task OpenContextForWriteAsync(Func OpenContextForWriteAsync(Func
+ /// Ensures that the for the "active" configuration has
+ /// been loaded by the time users and extensions can interact with the project.
+ ///
+ ///
+ /// It is important to make sure Roslyn is aware of the project by the time the project can be
+ /// interacted with so that restored documents and other features used quickly after solution
+ /// load behave correctly and have "project context".
+ ///
+ internal class WorkspaceProjectContextHostInitiator
+ {
+ private readonly IUnconfiguredProjectTasksService _tasksService;
+ private readonly IActiveWorkspaceProjectContextHost _activeWorkspaceProjectContextHost;
+
+ [ImportingConstructor]
+ public WorkspaceProjectContextHostInitiator(IUnconfiguredProjectTasksService tasksService, IActiveWorkspaceProjectContextHost activeWorkspaceProjectContextHost)
+ {
+ _tasksService = tasksService;
+ _activeWorkspaceProjectContextHost = activeWorkspaceProjectContextHost;
+ }
+
+ [ProjectAutoLoad(startAfter: ProjectLoadCheckpoint.AfterLoadInitialConfiguration, completeBy: ProjectLoadCheckpoint.ProjectFactoryCompleted)]
+ [AppliesTo(ProjectCapability.DotNetLanguageService)]
+ public Task InitializeAsync()
+ {
+ // While we want make sure it's loaded before PrioritizedProjectLoadedInHost,
+ // we don't want to block project factory completion on its load, so fire and forget
+ _tasksService.PrioritizedProjectLoadedInHostAsync(() => _activeWorkspaceProjectContextHost.PublishAsync())
+ .Forget();
+
+ return Task.CompletedTask;
+ }
+ }
+}