Workspace APIs unusable on Blazor WebAssembly under .NET 11: AddProject takes a blocking SemaphoreSlim.Wait
- Dominant language
- C#
- Stars
- 20.7k
- Forks
- 4.3k
- PR merge metrics
- PR metrics pending
Description
**Version Used**: `Microsoft.CodeAnalysis.CSharp.Workspaces` 5.3.0 (also reproduces on 5.6.0)
Minimal repro: https://github.com/SimonCropp/roslyn-wasm-blocking-wait
## Summary
`AdhocWorkspace.AddProject` throws `PlatformNotSupportedException` on single-threaded Blazor WebAssembly under .NET 11. The same call, with the same Roslyn version, succeeds on .NET 10. Since `AddProject` is the entry point to every workspace scenario, this means no Roslyn workspace can be constructed on browser-wasm at all under .NET 11.
## Steps to Reproduce
Adding one empty project is enough — no documents, no compilation, no completion:
```csharp
var workspace = new AdhocWorkspace();
workspace.AddProject(ProjectInfo.Create(
ProjectId.CreateNewId(), VersionStamp.Default, "Probe", "Probe", LanguageNames.CSharp));
```
The repro is two Blazor WebAssembly projects with `Workspace.cs` and `BlazorMain.cs` linked into both, so the code under test is byte-identical and the target framework is the only difference:
| Project | TFM | Roslyn | Result |
|---|---|---|---|
| `Net10` | net10.0 | 5.3.0 | AddProject succeeds |
| `Net11` | net11.0 | 5.3.0 | **`PlatformNotSupportedException`** |
```
dotnet run --project Net11/Net11.csproj
```
## Expected Behavior
`AddProject` succeeds, as it does on .NET 10.
## Actual Behavior
```
System.PlatformNotSupportedException: Arg_PlatformNotSupported
at System.Runtime.CompilerServices.RuntimeFeature.ThrowIfMultithreadingIsNotSupported()
at System.Threading.SemaphoreSlim.WaitCore(Int64 millisecondsTimeout, CancellationToken cancellationToken)
at System.Threading.SemaphoreSlim.Wait(CancellationToken cancellationToken)
at Roslyn.Utilities.SemaphoreSlimExtensions.DisposableWait(SemaphoreSlim semaphore, CancellationToken cancellationToken)
at Microsoft.CodeAnalysis.ProjectDependencyGraph.GetProjectsThatTransitivelyDependOnThisProject(ProjectId projectId)
at Microsoft.CodeAnalysis.SolutionCompilationState.AddProjects(ArrayBuilder`1 projectInfos)
at Microsoft.CodeAnalysis.Solution.AddProjects(ArrayBuilder`1 projectInfos)
at Microsoft.CodeAnalysis.Solution.AddProject(ProjectInfo projectInfo)
at Microsoft.CodeAnalysis.Workspace.CheckAndAddProject(Solution newSolution, ProjectInfo project)
...
at Microsoft.CodeAnalysis.Workspace.OnProjectAdded(ProjectInfo projectInfo)
at Microsoft.CodeAnalysis.AdhocWorkspace.AddProject(ProjectInfo projectInfo)
```
## What changed on the runtime side
`SemaphoreSlim.WaitCore` gained an unconditional multithreading check in .NET 11.
release/10.0 — guarded, so it only applied to the threaded wasm build:
```csharp
CheckDispose();
#if FEATURE_WASM_MANAGED_THREADS
Thread.AssureBlockingPossible();
#endif
```
main — unconditional, so it now also fires in the single-threaded build that Blazor WebAssembly uses by default:
```csharp
RuntimeFeature.ThrowIfMultithreadingIsNotSupported();
```
To be fair to the runtime side: `SemaphoreSlim.Wait` and `WaitCore` carry `[UnsupportedOSPlatform("browser")]` in release/10.0 too. That annotation is not new, so .NET 10 was not offering a supported guarantee — it simply never enforced the restriction outside the threaded build, and code taking an uncontended wait kept working.
That is why I am raising this here rather than only against the runtime: Roslyn is calling an API annotated unsupported-on-browser, on a path that browser-hosted callers cannot avoid.
## Question
Is it feasible for `ProjectDependencyGraph.GetProjectsThatTransitivelyDependOnThisProject` (and the `SetCurrentSolution` path that reaches it) to avoid a blocking `SemaphoreSlim.Wait`? The wait appears to be uncontended in the single-threaded case — it never actually blocks, which is precisely why this worked on .NET 10.
The documented alternative of enabling `WasmEnableThreads`, which would satisfy the runtime check, is not currently viable either: the .NET 11 runtime fails to start with threads enabled — dotnet/runtime#131311 (repro: https://github.com/SimonCropp/wasm-threads-startup-crash).
## Context
The affected app is the in-browser query explorer in [Scry](https://github.com/Papyrine/Scry), which hosts Roslyn in Blazor WebAssembly for completion over a synthesized query model. It works on .NET 10 and cannot initialize on .NET 11.
Environment: SDK 11.0.100-preview.6.26359.118, runtimes 11.0.0-preview.6.26359.118 and 10.0.9, Windows 11 x64, Chromium 148. Only .NET 11 preview 6 was tested; it is the latest published preview at time of writing.
Contributor guide
Research direction
Start with ProjectDependencyGraph.GetProjectsThatTransitivelyDependOnThisProject and the SetCurrentSolution path named in the report, then run dotnet run --project Net11/Net11.csproj from the linked minimal repro. Compare the net10.0 and net11.0 behavior and trace the DisposableWait call. Done means AdhocWorkspace.AddProject succeeds in single-threaded Blazor WebAssembly under .NET 11 without requiring wasm threads.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp, wasm
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100