public async Task <VisualStudioProject> CreateAndAddToWorkspaceAsync( string projectSystemName, string language, VisualStudioProjectCreationInfo creationInfo, CancellationToken cancellationToken) { // HACK: Fetch this service to ensure it's still created on the UI thread; once this is // moved off we'll need to fix up it's constructor to be free-threaded. await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); _visualStudioWorkspaceImpl.Services.GetRequiredService <VisualStudioMetadataReferenceManager>(); _visualStudioWorkspaceImpl.SubscribeExternalErrorDiagnosticUpdateSourceToSolutionBuildEvents(); // Since we're on the UI thread here anyways, use that as an opportunity to grab the // IVsSolution object and solution file path. // // ConfigureAwait(true) as we have to come back to the UI thread to do the cast to IVsSolution2. var solution = (IVsSolution2?)await _serviceProvider.GetServiceAsync(typeof(SVsSolution)).ConfigureAwait(true); var solutionFilePath = solution != null && ErrorHandler.Succeeded(solution.GetSolutionInfo(out _, out var filePath, out _)) ? filePath : null; // Following can be off the UI thread. await TaskScheduler.Default; // From this point on, we start mutating the solution. So make us non cancellable. cancellationToken = CancellationToken.None; var id = ProjectId.CreateNewId(projectSystemName); var assemblyName = creationInfo.AssemblyName ?? projectSystemName; // We will use the project system name as the default display name of the project var project = new VisualStudioProject( _visualStudioWorkspaceImpl, _dynamicFileInfoProviders, _hostDiagnosticUpdateSource, id, displayName: projectSystemName, language, assemblyName: assemblyName, compilationOptions: creationInfo.CompilationOptions, filePath: creationInfo.FilePath, parseOptions: creationInfo.ParseOptions); var versionStamp = creationInfo.FilePath != null?VersionStamp.Create(File.GetLastWriteTimeUtc(creationInfo.FilePath)) : VersionStamp.Create(); await _visualStudioWorkspaceImpl.ApplyChangeToWorkspaceAsync(w => { _visualStudioWorkspaceImpl.AddProjectToInternalMaps_NoLock(project, creationInfo.Hierarchy, creationInfo.ProjectGuid, projectSystemName); var projectInfo = ProjectInfo.Create( id, versionStamp, name: projectSystemName, assemblyName: assemblyName, language: language, filePath: creationInfo.FilePath, compilationOptions: creationInfo.CompilationOptions, parseOptions: creationInfo.ParseOptions) .WithTelemetryId(creationInfo.ProjectGuid); // If we don't have any projects and this is our first project being added, then we'll create a new SolutionId // and count this as the solution being added so that event is raised. if (w.CurrentSolution.ProjectIds.Count == 0) { var solutionSessionId = GetSolutionSessionId(); w.OnSolutionAdded( SolutionInfo.Create( SolutionId.CreateNewId(solutionFilePath), VersionStamp.Create(), solutionFilePath, projects: new[] { projectInfo }, analyzerReferences: w.CurrentSolution.AnalyzerReferences) .WithTelemetryId(solutionSessionId)); } else { w.OnProjectAdded(projectInfo); } }).ConfigureAwait(false); // Ensure that other VS contexts get accurate information that the UIContext for this language is now active. // This is not cancellable as we have already mutated the solution. await _visualStudioWorkspaceImpl.RefreshProjectExistsUIContextForLanguageAsync(language, CancellationToken.None).ConfigureAwait(false); return(project);