diff --git a/src/VisualStudio/Core/Def/LanguageService/AbstractPackage`2.cs b/src/VisualStudio/Core/Def/LanguageService/AbstractPackage`2.cs index 18417d5b6ed00..63d18eda90210 100644 --- a/src/VisualStudio/Core/Def/LanguageService/AbstractPackage`2.cs +++ b/src/VisualStudio/Core/Def/LanguageService/AbstractPackage`2.cs @@ -39,6 +39,7 @@ protected override void RegisterInitializeAsyncWork(PackageLoadTasks packageInit base.RegisterInitializeAsyncWork(packageInitializationTasks); packageInitializationTasks.AddTask(isMainThreadTask: true, task: PackageInitializationMainThreadAsync); + packageInitializationTasks.AddTask(isMainThreadTask: false, task: PackageInitializationBackgroundThreadAsync); } private async Task PackageInitializationMainThreadAsync(PackageLoadTasks packageInitializationTasks, CancellationToken cancellationToken) @@ -58,35 +59,33 @@ private async Task PackageInitializationMainThreadAsync(PackageLoadTasks package RegisterEditorFactory(editorFactory); } - // Misc workspace has to be up and running by the time our package is usable so that it can track running - // doc events and appropriately map files to/from it and other relevant workspaces (like the - // metadata-as-source workspace). - var miscellaneousFilesWorkspace = this.ComponentModel.GetService(); - // awaiting an IVsTask guarantees to return on the captured context await shell.LoadPackageAsync(Guids.RoslynPackageId); + } - packageInitializationTasks.AddTask( - isMainThreadTask: false, - task: (PackageLoadTasks packageInitializationTasks, CancellationToken cancellationToken) => - { - RegisterLanguageService(typeof(TLanguageService), async cancellationToken => - { - // Ensure we're on the BG when creating the language service. - await TaskScheduler.Default; + private Task PackageInitializationBackgroundThreadAsync(PackageLoadTasks packageInitializationTasks, CancellationToken cancellationToken) + { + RegisterLanguageService(typeof(TLanguageService), async cancellationToken => + { + // Ensure we're on the BG when creating the language service. + await TaskScheduler.Default; + + // Create the language service, tell it to set itself up, then store it in a field + // so we can notify it that it's time to clean up. + _languageService = CreateLanguageService(); + await _languageService.SetupAsync(cancellationToken).ConfigureAwait(false); - // Create the language service, tell it to set itself up, then store it in a field - // so we can notify it that it's time to clean up. - _languageService = CreateLanguageService(); - await _languageService.SetupAsync(cancellationToken).ConfigureAwait(false); + return _languageService.ComAggregate!; + }); - return _languageService.ComAggregate!; - }); + // Misc workspace has to be up and running by the time our package is usable so that it can track running + // doc events and appropriately map files to/from it and other relevant workspaces (like the + // metadata-as-source workspace). + var miscellaneousFilesWorkspace = this.ComponentModel.GetService(); - RegisterMiscellaneousFilesWorkspaceInformation(miscellaneousFilesWorkspace); + RegisterMiscellaneousFilesWorkspaceInformation(miscellaneousFilesWorkspace); - return Task.CompletedTask; - }); + return Task.CompletedTask; } protected override void RegisterOnAfterPackageLoadedAsyncWork(PackageLoadTasks afterPackageLoadedTasks) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MiscellaneousFilesWorkspace.cs b/src/VisualStudio/Core/Def/ProjectSystem/MiscellaneousFilesWorkspace.cs index b52774a0f090e..3c7e5df73308e 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MiscellaneousFilesWorkspace.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MiscellaneousFilesWorkspace.cs @@ -31,7 +31,7 @@ internal sealed partial class MiscellaneousFilesWorkspace : Workspace, IOpenText private readonly IThreadingContext _threadingContext; private readonly IVsService _textManagerService; private readonly OpenTextBufferProvider _openTextBufferProvider; - private readonly IMetadataAsSourceFileService _fileTrackingMetadataAsSourceService; + private readonly Lazy _fileTrackingMetadataAsSourceService; private readonly ConcurrentDictionary _languageInformationByLanguageGuid = []; @@ -47,7 +47,7 @@ internal sealed partial class MiscellaneousFilesWorkspace : Workspace, IOpenText /// private readonly Dictionary _monikersToProjectIdAndContainer = []; - private readonly ImmutableArray _metadataReferences; + private readonly Lazy> _metadataReferences; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] @@ -55,16 +55,16 @@ public MiscellaneousFilesWorkspace( IThreadingContext threadingContext, IVsService textManagerService, OpenTextBufferProvider openTextBufferProvider, - IMetadataAsSourceFileService fileTrackingMetadataAsSourceService, - VisualStudioWorkspace visualStudioWorkspace) - : base(visualStudioWorkspace.Services.HostServices, WorkspaceKind.MiscellaneousFiles) + Lazy fileTrackingMetadataAsSourceService, + Composition.ExportProvider exportProvider) + : base(VisualStudioMefHostServices.Create(exportProvider), WorkspaceKind.MiscellaneousFiles) { _threadingContext = threadingContext; _textManagerService = textManagerService; _openTextBufferProvider = openTextBufferProvider; _fileTrackingMetadataAsSourceService = fileTrackingMetadataAsSourceService; - _metadataReferences = [.. CreateMetadataReferences()]; + _metadataReferences = new(() => [.. CreateMetadataReferences()]); _openTextBufferProvider.AddListener(this); } @@ -122,6 +122,10 @@ public void RegisterLanguage(Guid languageGuid, string languageName, string scri private IEnumerable CreateMetadataReferences() { + // VisualStudioMetadataReferenceManager construction requires the main thread + // TODO: Determine if main thread affinity can be removed: https://github.com/dotnet/roslyn/issues/77791 + _threadingContext.ThrowIfNotOnUIThread(); + var manager = this.Services.GetService(); var searchPaths = VisualStudioMetadataReferenceManager.GetReferencePaths(); @@ -261,7 +265,7 @@ private void AttachToDocument(string moniker, ITextBuffer textBuffer) { _threadingContext.ThrowIfNotOnUIThread(); - if (_fileTrackingMetadataAsSourceService.TryAddDocumentToWorkspace(moniker, textBuffer.AsTextContainer(), out var _)) + if (_fileTrackingMetadataAsSourceService.Value.TryAddDocumentToWorkspace(moniker, textBuffer.AsTextContainer(), out var _)) { // We already added it, so we will keep it excluded from the misc files workspace return; @@ -282,6 +286,10 @@ private void AttachToDocument(string moniker, ITextBuffer textBuffer) /// private ProjectInfo CreateProjectInfoForDocument(string filePath) { + // Potential calculation of _metadataReferences requires being on the main thread + // TODO: Determine if main thread affinity can be removed: https://github.com/dotnet/roslyn/issues/77791 + _threadingContext.ThrowIfNotOnUIThread(); + // This should always succeed since we only got here if we already confirmed the moniker is acceptable var languageInformation = TryGetLanguageInformation(filePath); Contract.ThrowIfNull(languageInformation); @@ -289,13 +297,13 @@ private ProjectInfo CreateProjectInfoForDocument(string filePath) var checksumAlgorithm = SourceHashAlgorithms.Default; var fileLoader = new WorkspaceFileTextLoader(Services.SolutionServices, filePath, defaultEncoding: null); return MiscellaneousFileUtilities.CreateMiscellaneousProjectInfoForDocument( - this, filePath, fileLoader, languageInformation, checksumAlgorithm, Services.SolutionServices, _metadataReferences); + this, filePath, fileLoader, languageInformation, checksumAlgorithm, Services.SolutionServices, _metadataReferences.Value); } private void DetachFromDocument(string moniker) { _threadingContext.ThrowIfNotOnUIThread(); - if (_fileTrackingMetadataAsSourceService.TryRemoveDocumentFromWorkspace(moniker)) + if (_fileTrackingMetadataAsSourceService.Value.TryRemoveDocumentFromWorkspace(moniker)) { return; }