Description
On single-threaded browser-wasm (.NET 11 preview), SemaphoreSlim.Wait(0) — a zero-timeout,
non-blocking try-acquire — throws PlatformNotSupportedException whenever the semaphore has an
available count. Only the fail-fast branch escapes.
In SemaphoreSlim.WaitCore the order of checks is:
if (millisecondsTimeout == 0 && m_currentCount == 0) return false; — fast-fail path, no guard
RuntimeFeature.ThrowIfMultithreadingIsNotSupported(); — hit unconditionally otherwise
So Wait(0) against an available semaphore — an operation that by definition cannot block —
walks into the multithreading guard and throws:
System.PlatformNotSupportedException: Arg_PlatformNotSupported
at System.Runtime.CompilerServices.RuntimeFeature.ThrowIfMultithreadingIsNotSupported()
at System.Threading.SemaphoreSlim.WaitCore(Int64 millisecondsTimeout, CancellationToken cancellationToken)
at System.Threading.SemaphoreSlim.Wait(Int32 millisecondsTimeout)
Reproduction
Blazor WebAssembly app on .NET 11 preview (observed on 11.0.100-preview.6 SDK, browser-wasm,
single-threaded):
new SemaphoreSlim(1).Wait(0); // throws PlatformNotSupportedException; returned true on net10 wasm
Impact
Grpc.Net.Client ≥ 2.x calls _connectSemaphore.Wait(0) in
Grpc.Net.Client.Balancer.Subchannel.ConnectTransportAsync on every channel's first connect
(load balancing is compiled into the TFM build Blazor serves, and BalancerHttpHandler wraps every
channel). On the first connect the semaphore is always available → the guard always throws → the
exception dies inside grpc-dotnet's fire-and-forget connect task (_ = RunCall(...)) → every
gRPC-Web call from Blazor WebAssembly hangs forever with zero observable errors: no console
output, no network request, an await that never completes. The same package versions work on
desktop and on the .NET 10 WASM runtime.
Expected behavior
A zero-timeout try-acquire cannot block and should not be gated on multithreading support:
the guard should sit behind the immediate-acquire fast path (mirror of the existing
fail-fast path), i.e. Wait(0) should return true/false on browser-wasm exactly as it did
through .NET 10.
Notes
Description
On single-threaded browser-wasm (.NET 11 preview),
SemaphoreSlim.Wait(0)— a zero-timeout,non-blocking try-acquire — throws
PlatformNotSupportedExceptionwhenever the semaphore has anavailable count. Only the fail-fast branch escapes.
In
SemaphoreSlim.WaitCorethe order of checks is:if (millisecondsTimeout == 0 && m_currentCount == 0) return false;— fast-fail path, no guardRuntimeFeature.ThrowIfMultithreadingIsNotSupported();— hit unconditionally otherwiseSo
Wait(0)against an available semaphore — an operation that by definition cannot block —walks into the multithreading guard and throws:
Reproduction
Blazor WebAssembly app on .NET 11 preview (observed on 11.0.100-preview.6 SDK, browser-wasm,
single-threaded):
Impact
Grpc.Net.Client≥ 2.x calls_connectSemaphore.Wait(0)inGrpc.Net.Client.Balancer.Subchannel.ConnectTransportAsyncon every channel's first connect(load balancing is compiled into the TFM build Blazor serves, and
BalancerHttpHandlerwraps everychannel). On the first connect the semaphore is always available → the guard always throws → the
exception dies inside grpc-dotnet's fire-and-forget connect task (
_ = RunCall(...)) → everygRPC-Web call from Blazor WebAssembly hangs forever with zero observable errors: no console
output, no network request, an await that never completes. The same package versions work on
desktop and on the .NET 10 WASM runtime.
Expected behavior
A zero-timeout try-acquire cannot block and should not be gated on multithreading support:
the guard should sit behind the immediate-acquire fast path (mirror of the existing
fail-fast path), i.e.
Wait(0)should return true/false on browser-wasm exactly as it didthrough .NET 10.
Notes
Wait(0)on the browser path, and connect-task exceptions being unobservable).