diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj
index 7cd940f242fc8b..95a963f1037872 100644
--- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj
+++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj
@@ -18,10 +18,6 @@
true$(IntermediateOutputPath)ILLink.Descriptors.xml$(MSBuildThisFileDirectory)src\ILLink\
-
- true
- true
- true
@@ -303,6 +299,12 @@
+
+
+
+
+
+
diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs
index b29bf304f3811b..a48ff67a654fa8 100644
--- a/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs
+++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs
@@ -145,6 +145,8 @@ private void StartCallback()
///
public static void SpinWait(int iterations)
{
+ if (Thread.IsSingleThreaded) return;
+
if (iterations < SpinWaitCoopThreshold)
{
SpinWaitInternal(iterations);
diff --git a/src/coreclr/clr.featuredefines.props b/src/coreclr/clr.featuredefines.props
index 1a07f0aa7c1225..4f7e9114d968ce 100644
--- a/src/coreclr/clr.featuredefines.props
+++ b/src/coreclr/clr.featuredefines.props
@@ -5,6 +5,8 @@
falsefalsetrue
+ true
+ true
@@ -15,6 +17,13 @@
truetruetrue
+ true
+
+
+ true
+ false
+ false
+
false
@@ -56,6 +65,8 @@
$(DefineConstants);FEATURE_INTERPRETER$(DefineConstants);FEATURE_PORTABLE_ENTRYPOINTS$(DefineConstants);FEATURE_PORTABLE_HELPERS
+ $(DefineConstants);FEATURE_WASM_MANAGED_THREADS
+ $(DefineConstants);FEATURE_SINGLE_THREADED$(DefineConstants);PROFILING_SUPPORTED
diff --git a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs
index 8bd3953dc82d12..30c1cd022a9d9c 100644
--- a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs
+++ b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs
@@ -232,7 +232,8 @@ private static bool GetLinqExpressionsBuiltWithIsInterpretingOnly()
// heavily on Reflection.Emit
public static bool IsXmlDsigXsltTransformSupported => !PlatformDetection.IsInAppContainer && IsReflectionEmitSupported;
- public static bool IsPreciseGcSupported => !IsMonoRuntime;
+ public static bool IsPreciseGcSupported => !IsMonoRuntime
+ && !IsBrowser; // TODO-WASM: https://github.com/dotnet/runtime/issues/114096
public static bool IsRareEnumsSupported => !IsNativeAot;
diff --git a/src/libraries/Microsoft.Extensions.Configuration.Xml/src/XmlDocumentDecryptor.cs b/src/libraries/Microsoft.Extensions.Configuration.Xml/src/XmlDocumentDecryptor.cs
index 24f88d5166de9a..da0690e56b2ec3 100644
--- a/src/libraries/Microsoft.Extensions.Configuration.Xml/src/XmlDocumentDecryptor.cs
+++ b/src/libraries/Microsoft.Extensions.Configuration.Xml/src/XmlDocumentDecryptor.cs
@@ -103,7 +103,7 @@ public XmlReader CreateDecryptingXmlReader(Stream input, XmlReaderSettings? sett
protected virtual XmlReader DecryptDocumentAndCreateXmlReader(XmlDocument document)
{
#if !NETSTANDARD2_1 && !NETSTANDARD2_0 && !NETFRAMEWORK // TODO remove with https://github.com/dotnet/runtime/pull/107185
- if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException();
+ if (OperatingSystem.IsWasi() || OperatingSystem.IsBrowser()) throw new PlatformNotSupportedException();
#else
#pragma warning disable CA1416
#endif
diff --git a/src/libraries/Microsoft.Extensions.Hosting.Systemd/src/SystemdNotifier.cs b/src/libraries/Microsoft.Extensions.Hosting.Systemd/src/SystemdNotifier.cs
index a27a7c9154e675..791eda2a8413af 100644
--- a/src/libraries/Microsoft.Extensions.Hosting.Systemd/src/SystemdNotifier.cs
+++ b/src/libraries/Microsoft.Extensions.Hosting.Systemd/src/SystemdNotifier.cs
@@ -42,7 +42,7 @@ public void Notify(ServiceState state)
}
#if !NETSTANDARD2_1 && !NETSTANDARD2_0 && !NETFRAMEWORK // TODO remove with https://github.com/dotnet/runtime/pull/107185
- if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException();
+ if (OperatingSystem.IsWasi() || OperatingSystem.IsBrowser()) throw new PlatformNotSupportedException();
#else
#pragma warning disable CA1416
#endif
diff --git a/src/libraries/System.Collections.Concurrent/tests/BlockingCollectionCancellationTests.cs b/src/libraries/System.Collections.Concurrent/tests/BlockingCollectionCancellationTests.cs
index acba0d0f485ce8..a5c48680d803a1 100644
--- a/src/libraries/System.Collections.Concurrent/tests/BlockingCollectionCancellationTests.cs
+++ b/src/libraries/System.Collections.Concurrent/tests/BlockingCollectionCancellationTests.cs
@@ -8,9 +8,10 @@
namespace System.Collections.Concurrent.Tests
{
+ [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
public class BlockingCollectionCancellationTests
{
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [Fact]
public static void InternalCancellation_CompleteAdding_Negative()
{
BlockingCollection coll1 = new BlockingCollection();
@@ -26,7 +27,7 @@ public static void InternalCancellation_CompleteAdding_Negative()
}
//This tests that Take/TryTake wake up correctly if CompleteAdding() is called while waiting
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [Fact]
public static void InternalCancellation_WakingUp()
{
for (int test = 0; test < 2; test++)
@@ -60,7 +61,7 @@ public static void InternalCancellation_WakingUp()
}
}
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [Fact]
public static void ExternalCancel_Negative()
{
BlockingCollection bc = new BlockingCollection(); //empty collection.
@@ -96,7 +97,7 @@ public static void ExternalCancel_Negative()
cs.Token);
}
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [Fact]
public static void ExternalCancel_AddToAny()
{
for (int test = 0; test < 3; test++)
diff --git a/src/libraries/System.Collections.Concurrent/tests/BlockingCollectionTests.cs b/src/libraries/System.Collections.Concurrent/tests/BlockingCollectionTests.cs
index 893784a05e0361..d9c202b4f9df05 100644
--- a/src/libraries/System.Collections.Concurrent/tests/BlockingCollectionTests.cs
+++ b/src/libraries/System.Collections.Concurrent/tests/BlockingCollectionTests.cs
@@ -13,9 +13,10 @@
namespace System.Collections.Concurrent.Tests
{
/// The class that contains the unit tests of the BlockingCollection.
+ [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
public class BlockingCollectionTests
{
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [Fact]
public static void TestBasicScenarios()
{
BlockingCollection bc = new BlockingCollection(3);
@@ -53,7 +54,7 @@ public static void TestBasicScenarios()
/// BlockingCollection throws InvalidOperationException when calling CompleteAdding even after adding and taking all elements
///
///
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [Fact]
public static void TestBugFix544259()
{
int count = 8;
@@ -88,7 +89,7 @@ public static void TestBugFix544259()
// Since the change to wait as part of CTS.Dispose, the ODE no longer occurs
// but we keep the test as a good example of how cleanup of linkedCTS must be carefully handled
// to prevent users of the source CTS mistakenly calling methods on disposed targets.
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [Fact]
public static void TestBugFix626345()
{
const int noOfProducers = 1;
@@ -152,7 +153,7 @@ public static void TestBugFix626345()
///
/// Making sure if TryTakeFromAny succeeds, it returns the correct index
///
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [Fact]
public static void TestBugFix914998()
{
var producer1 = new BlockingCollection();
@@ -252,7 +253,7 @@ public static void TestAddTake_Longrunning(int numOfAdds, int numOfTakes, int bo
/// present in the collection.
/// Number of producer threads.
/// Number of elements added per thread.
- [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [Theory]
[InlineData(2, 1024)]
[InlineData(8, 512)]
public static void TestConcurrentAdd(int numOfThreads, int numOfElementsPerThread)
@@ -291,7 +292,7 @@ public static void TestConcurrentAdd(int numOfThreads, int numOfElementsPerThrea
/// are consumed by consumers with no element lost nor consumed more than once.
/// Total number of producer and consumer threads.
/// Number of elements to Add/Take per thread.
- [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [Theory]
[InlineData(8, 1024)]
public static void TestConcurrentAddTake(int numOfThreads, int numOfElementsPerThread)
{
@@ -534,7 +535,7 @@ public static void Test7_CompleteAdding()
Assert.Equal(0, counter);
}
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [Fact]
public static void Test7_ConcurrentAdd_CompleteAdding()
{
BlockingCollection blockingCollection = ConstructBlockingCollection();
@@ -741,7 +742,7 @@ public static void TestAddAnyTakeAny_Longrunning(int numOfAdds, int numOfTakes,
/// are consumed by consumers with no element lost nor consumed more than once.
/// Total number of producer and consumer threads.
/// Number of elements to Add/Take per thread.
- [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [Theory]
[InlineData(4, 2048, 2, 64)]
[OuterLoop]
private static void TestConcurrentAddAnyTakeAny(int numOfThreads, int numOfElementsPerThread, int numOfCollections, int boundOfCollections)
diff --git a/src/libraries/System.Linq.Parallel/src/System.Linq.Parallel.csproj b/src/libraries/System.Linq.Parallel/src/System.Linq.Parallel.csproj
index 173fae9740b234..fdf895c25448ff 100644
--- a/src/libraries/System.Linq.Parallel/src/System.Linq.Parallel.csproj
+++ b/src/libraries/System.Linq.Parallel/src/System.Linq.Parallel.csproj
@@ -11,6 +11,8 @@
$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))true$(DefineConstants);FEATURE_WASM_MANAGED_THREADS
+ true
+ $(DefineConstants);FEATURE_SINGLE_THREADED
diff --git a/src/libraries/System.Linq.Parallel/src/System/Linq/ParallelEnumerable.cs b/src/libraries/System.Linq.Parallel/src/System/Linq/ParallelEnumerable.cs
index 9f3dc2674aab52..48b88769467f51 100644
--- a/src/libraries/System.Linq.Parallel/src/System/Linq/ParallelEnumerable.cs
+++ b/src/libraries/System.Linq.Parallel/src/System/Linq/ParallelEnumerable.cs
@@ -18,6 +18,7 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Parallel;
+using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;
@@ -64,9 +65,11 @@ public static class ParallelEnumerable
// When running in single partition mode, PLINQ operations will occur on a single partition and will not
// be executed in parallel, but will retain PLINQ semantics (exceptions wrapped as aggregates, etc).
-#if !FEATURE_WASM_MANAGED_THREADS
- [System.Runtime.Versioning.SupportedOSPlatformGuard("browser")]
- internal static bool SinglePartitionMode => OperatingSystem.IsBrowser() || OperatingSystem.IsWasi();
+
+ [SupportedOSPlatformGuard("browser")]
+ [SupportedOSPlatformGuard("wasi")]
+#if FEATURE_SINGLE_THREADED
+ internal static bool SinglePartitionMode => true;
#else
internal static bool SinglePartitionMode => false;
#endif
diff --git a/src/libraries/System.Net.WebSockets/tests/WebSocketDeflateTests.cs b/src/libraries/System.Net.WebSockets/tests/WebSocketDeflateTests.cs
index d0fa5bea4a4a5d..5d13e583323003 100644
--- a/src/libraries/System.Net.WebSockets/tests/WebSocketDeflateTests.cs
+++ b/src/libraries/System.Net.WebSockets/tests/WebSocketDeflateTests.cs
@@ -67,7 +67,7 @@ public async Task ReceiveHelloWithContextTakeover()
Assert.Equal("Hello", Encoding.UTF8.GetString(buffer.Span));
}
- [Fact]
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
public async Task SendHelloWithContextTakeover()
{
WebSocketTestStream stream = new();
@@ -164,7 +164,7 @@ public async Task ReceiveHelloWithoutContextTakeover()
}
}
- [Fact]
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
public async Task SendHelloWithoutContextTakeover()
{
WebSocketTestStream stream = new();
diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.cs
index 11c712fc68d6c1..08b49c3dedf763 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Environment.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Environment.cs
@@ -35,15 +35,15 @@ public readonly struct ProcessCpuUsage
public TimeSpan TotalTime => UserTime + PrivilegedTime;
}
- public static int ProcessorCount { get; } = GetProcessorCount();
-
///
/// Gets whether the current machine has only a single processor.
///
-#if !FEATURE_SINGLE_THREADED
- internal static bool IsSingleProcessor => ProcessorCount == 1;
-#else
+#if FEATURE_SINGLE_THREADED
internal const bool IsSingleProcessor = true;
+ public static int ProcessorCount => 1;
+#else
+ internal static bool IsSingleProcessor => ProcessorCount == 1;
+ public static int ProcessorCount { get; } = GetProcessorCount();
#endif
private static volatile sbyte s_privilegedProcess;
diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Stream.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Stream.cs
index 4a4c00efd30137..66184e0dfdcc1d 100644
--- a/src/libraries/System.Private.CoreLib/src/System/IO/Stream.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/IO/Stream.cs
@@ -216,15 +216,16 @@ internal Task BeginReadInternal(
// thread if it does a second IO request until the first one completes.
SemaphoreSlim semaphore = EnsureAsyncActiveSemaphoreInitialized();
Task? semaphoreTask = null;
- if (serializeAsynchronously)
+
+ // The synchronous path is emulating legacy behavior.
+ // Drop the emulation for IsSingleThreaded to avoid throwing.
+ if (Thread.IsSingleThreaded || serializeAsynchronously)
{
semaphoreTask = semaphore.WaitAsync();
}
else
{
-#pragma warning disable CA1416 // Validate platform compatibility, issue: https://github.com/dotnet/runtime/issues/44543
semaphore.Wait();
-#pragma warning restore CA1416
}
// Create the task to asynchronously do a Read. This task serves both
@@ -490,15 +491,16 @@ internal Task BeginWriteInternal(
// thread if it does a second IO request until the first one completes.
SemaphoreSlim semaphore = EnsureAsyncActiveSemaphoreInitialized();
Task? semaphoreTask = null;
- if (serializeAsynchronously)
+
+ // The synchronous path is emulating legacy behavior.
+ // Drop the emulation for IsSingleThreaded to avoid throwing.
+ if (Thread.IsSingleThreaded || serializeAsynchronously)
{
semaphoreTask = semaphore.WaitAsync(); // kick off the asynchronous wait, but don't block
}
else
{
-#pragma warning disable CA1416 // Validate platform compatibility, issue: https://github.com/dotnet/runtime/issues/44543
semaphore.Wait(); // synchronously wait here
-#pragma warning restore CA1416
}
// Create the task to asynchronously do a Write. This task serves both
diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/TextWriter.cs b/src/libraries/System.Private.CoreLib/src/System/IO/TextWriter.cs
index 59d5a46fcd7e61..8f879a79a6701d 100644
--- a/src/libraries/System.Private.CoreLib/src/System/IO/TextWriter.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/IO/TextWriter.cs
@@ -883,11 +883,9 @@ public static TextWriter Synchronized(TextWriter writer)
{
ArgumentNullException.ThrowIfNull(writer);
-#if (!TARGET_BROWSER && !TARGET_WASI) || FEATURE_WASM_MANAGED_THREADS
- return writer is SyncTextWriter ? writer : new SyncTextWriter(writer);
-#else
- return writer;
-#endif
+ return Thread.IsSingleThreaded || writer is SyncTextWriter
+ ? writer
+ : new SyncTextWriter(writer);
}
internal sealed class SyncTextWriter : TextWriter, IDisposable
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.cs
index 584df1d63b99d9..2ac672238f74d9 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.cs
@@ -362,7 +362,7 @@ internal static void ResetThreadPoolThread(Thread currentThread)
[Conditional("DEBUG")]
internal static void CheckThreadPoolAndContextsAreDefault()
{
- Debug.Assert(!Thread.IsThreadStartSupported || Thread.CurrentThread.IsThreadPoolThread); // there are no dedicated threadpool threads on runtimes where we can't start threads
+ Debug.Assert(Thread.IsSingleThreaded || Thread.CurrentThread.IsThreadPoolThread); // there are no dedicated threadpool threads on runtimes where we can't start threads
Debug.Assert(Thread.CurrentThread._executionContext == null, "ThreadPool thread not on Default ExecutionContext.");
Debug.Assert(Thread.CurrentThread._synchronizationContext == null, "ThreadPool thread not on Default SynchronizationContext.");
}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Lock.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Lock.cs
index e43baa789d3e75..db99731ec0d950 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/Lock.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Lock.cs
@@ -519,6 +519,8 @@ internal int TryEnterSlow(int timeoutMs, int currentThreadId)
goto Locked;
}
+ Thread.ThrowIfSingleThreaded();
+
// Lock was not acquired and a waiter was registered. All following paths need to unregister the waiter, including
// exceptional paths.
try
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.cs
index 73464549ff9dea..89ca032236e7a4 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.cs
@@ -37,10 +37,6 @@ public bool Wait(int timeoutMs)
{
Debug.Assert(timeoutMs >= -1);
-#if FEATURE_WASM_MANAGED_THREADS
- Thread.AssureBlockingPossible();
-#endif
-
// Try one-shot acquire first
Counts counts = _separated._counts;
if (counts.SignalCount != 0)
@@ -55,6 +51,8 @@ public bool Wait(int timeoutMs)
}
}
+ Thread.ThrowIfSingleThreaded();
+
return WaitSlow(timeoutMs);
}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLock.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLock.cs
index c2e970c27d955f..93fa0d54f44aee 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLock.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLock.cs
@@ -153,6 +153,8 @@ private void WaitAndAcquire()
{
VerifyIsNotLocked();
+ Thread.ThrowIfSingleThreaded();
+
// Spin a bit to see if the lock becomes available, before forcing the thread into a wait state
if (_spinWaiter.SpinWaitForCondition(s_spinWaitTryAcquireCallback, this, SpinCount, SpinSleep0Threshold))
{
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ManualResetEventSlim.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ManualResetEventSlim.cs
index 54b6596d6a4c94..5d3327f343ca6a 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/ManualResetEventSlim.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ManualResetEventSlim.cs
@@ -158,7 +158,7 @@ public ManualResetEventSlim(bool initialState)
{
// Specify the default spin count, and use default spin if we're
// on a multi-processor machine. Otherwise, we won't.
- Initialize(initialState, SpinWait.SpinCountforSpinBeforeWait);
+ Initialize(initialState, SpinWait.SpinCountForSpinBeforeWait);
}
///
@@ -352,9 +352,6 @@ public void Reset()
#endif
public void Wait()
{
-#if TARGET_WASI
- if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
-#endif
Wait(Timeout.Infinite, CancellationToken.None);
}
@@ -491,12 +488,7 @@ public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken)
ArgumentOutOfRangeException.ThrowIfLessThan(millisecondsTimeout, -1);
-#if TARGET_WASI
- if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
-#endif
-#if FEATURE_WASM_MANAGED_THREADS
- Thread.AssureBlockingPossible();
-#endif
+ Thread.ThrowIfSingleThreaded();
if (!IsSet)
{
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Monitor.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Monitor.cs
index d83a51f2a0f9cd..4aa19dda87e8a6 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/Monitor.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Monitor.cs
@@ -97,6 +97,7 @@ private static Condition GetCondition(object obj)
[UnsupportedOSPlatform("browser")]
public static bool Wait(object obj, int millisecondsTimeout)
{
+ Thread.ThrowIfSingleThreaded();
return GetCondition(obj).Wait(millisecondsTimeout, obj);
}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Portable.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Portable.cs
index 574dbf5ee2ed28..1e57f434d6f834 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Portable.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Portable.cs
@@ -55,15 +55,12 @@ public sealed partial class RegisteredWaitHandle : MarshalByRefObject
internal RegisteredWaitHandle(WaitHandle waitHandle, _ThreadPoolWaitOrTimerCallback callbackHelper,
int millisecondsTimeout, bool repeating)
{
-#if TARGET_WASI
- if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
-#endif
+ Thread.ThrowIfSingleThreaded();
#if TARGET_WINDOWS
Debug.Assert(!ThreadPool.UseWindowsThreadPool);
#endif
GC.SuppressFinalize(this);
- Thread.ThrowIfNoThreadStart();
_waitHandle = waitHandle.SafeWaitHandle;
_callbackHelper = callbackHelper;
_signedMillisecondsTimeout = millisecondsTimeout;
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/SemaphoreSlim.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/SemaphoreSlim.cs
index 69fbef6d57dea8..a55ff012d9eecc 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/SemaphoreSlim.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/SemaphoreSlim.cs
@@ -174,9 +174,6 @@ public SemaphoreSlim(int initialCount, int maxCount)
[UnsupportedOSPlatform("browser")]
public void Wait()
{
-#if TARGET_WASI
- if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
-#endif
// Call wait with infinite timeout
WaitCore(Timeout.Infinite, CancellationToken.None);
}
@@ -194,9 +191,6 @@ public void Wait()
[UnsupportedOSPlatform("browser")]
public void Wait(CancellationToken cancellationToken)
{
-#if TARGET_WASI
- if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
-#endif
// Call wait with infinite timeout
WaitCore(Timeout.Infinite, cancellationToken);
}
@@ -215,9 +209,6 @@ public void Wait(CancellationToken cancellationToken)
[UnsupportedOSPlatform("browser")]
public bool Wait(TimeSpan timeout)
{
-#if TARGET_WASI
- if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
-#endif
// Validate the timeout
long totalMilliseconds = (long)timeout.TotalMilliseconds;
if (totalMilliseconds < -1)
@@ -248,9 +239,6 @@ public bool Wait(TimeSpan timeout)
[UnsupportedOSPlatform("browser")]
public bool Wait(TimeSpan timeout, CancellationToken cancellationToken)
{
-#if TARGET_WASI
- if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
-#endif
// Validate the timeout
long totalMilliseconds = (long)timeout.TotalMilliseconds;
if (totalMilliseconds < -1)
@@ -276,9 +264,6 @@ public bool Wait(TimeSpan timeout, CancellationToken cancellationToken)
[UnsupportedOSPlatform("browser")]
public bool Wait(int millisecondsTimeout)
{
-#if TARGET_WASI
- if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
-#endif
if (millisecondsTimeout < -1)
{
throw new ArgumentOutOfRangeException(
@@ -302,10 +287,6 @@ public bool Wait(int millisecondsTimeout)
[UnsupportedOSPlatform("browser")]
public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken)
{
-#if TARGET_WASI
- if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
-#endif
-
if (millisecondsTimeout < -1)
{
throw new ArgumentOutOfRangeException(
@@ -329,9 +310,7 @@ public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken)
private bool WaitCore(long millisecondsTimeout, CancellationToken cancellationToken)
{
CheckDispose();
-#if FEATURE_WASM_MANAGED_THREADS
- Thread.AssureBlockingPossible();
-#endif
+
cancellationToken.ThrowIfCancellationRequested();
// Perf: Check the stack timeout parameter before checking the volatile count
@@ -341,6 +320,8 @@ private bool WaitCore(long millisecondsTimeout, CancellationToken cancellationTo
return false;
}
+ Thread.ThrowIfSingleThreaded();
+
long startTime = 0;
if (millisecondsTimeout != Timeout.Infinite && millisecondsTimeout > 0)
{
@@ -365,7 +346,7 @@ private bool WaitCore(long millisecondsTimeout, CancellationToken cancellationTo
// Monitor.Enter followed by Monitor.Wait is much more expensive than waiting on an event as it involves another
// spin, contention, etc. The usual number of spin iterations that would otherwise be used here is increased to
// lessen that extra expense of doing a proper wait.
- int spinCount = SpinWait.SpinCountforSpinBeforeWait * 4;
+ int spinCount = SpinWait.SpinCountForSpinBeforeWait * 4;
SpinWait spinner = default;
while (spinner.Count < spinCount)
@@ -470,9 +451,8 @@ private bool WaitCore(long millisecondsTimeout, CancellationToken cancellationTo
[UnsupportedOSPlatform("browser")]
private bool WaitUntilCountOrTimeout(long millisecondsTimeout, long startTime, CancellationToken cancellationToken)
{
-#if TARGET_WASI
- if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
-#endif
+ Thread.ThrowIfSingleThreaded();
+
int monitorWaitMilliseconds = Timeout.Infinite;
// Wait on the monitor as long as the count is zero
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/SpinWait.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/SpinWait.cs
index 4246a33191d10b..1862346abdb481 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/SpinWait.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/SpinWait.cs
@@ -86,9 +86,9 @@ public struct SpinWait
/// for here.
///
#if FEATURE_SINGLE_THREADED
- internal const int SpinCountforSpinBeforeWait = 1;
+ internal const int SpinCountForSpinBeforeWait = 1;
#else
- internal static readonly int SpinCountforSpinBeforeWait = Environment.IsSingleProcessor ? 1 : 35;
+ internal static readonly int SpinCountForSpinBeforeWait = Environment.IsSingleProcessor ? 1 : 35;
#endif
// The number of times we've spun already.
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/FutureFactory.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/FutureFactory.cs
index 088f359877badb..487af7c324cf5b 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/FutureFactory.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/FutureFactory.cs
@@ -676,6 +676,8 @@ internal static Task FromAsyncImpl(
}
else
{
+ Thread.ThrowIfSingleThreaded();
+
#pragma warning disable CA1416 // Validate platform compatibility, issue: https://github.com/dotnet/runtime/issues/44544
ThreadPool.RegisterWaitForSingleObject(
asyncResult.AsyncWaitHandle,
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs
index 1cd3b2e3b540dd..1e64b0c17a51e2 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs
@@ -2969,9 +2969,6 @@ internal bool InternalWait(int millisecondsTimeout, CancellationToken cancellati
// to be able to see the method on the stack and inspect arguments).
private bool InternalWaitCore(int millisecondsTimeout, CancellationToken cancellationToken)
{
-#if TARGET_WASI
- if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
-#endif
// If the task has already completed, there's nothing to wait for.
bool returnValue = IsCompleted;
if (returnValue)
@@ -3007,6 +3004,8 @@ private bool InternalWaitCore(int millisecondsTimeout, CancellationToken cancell
}
else
{
+ Thread.ThrowIfSingleThreaded();
+
returnValue = SpinThenBlockingWait(millisecondsTimeout, cancellationToken);
}
@@ -3062,6 +3061,8 @@ private bool SpinThenBlockingWait(int millisecondsTimeout, CancellationToken can
bool returnValue = SpinWait(millisecondsTimeout);
if (!returnValue)
{
+ Thread.ThrowIfSingleThreaded();
+
// We're about to block waiting for the task to complete, which is expensive, and if
// the task being waited on depends on some other work to run, this thread could end up
// waiting for some other thread to do work. If the two threads are part of the same scheduler,
@@ -3140,13 +3141,18 @@ private bool SpinWait(int millisecondsTimeout)
{
if (IsCompleted) return true;
+ if (Thread.IsSingleThreaded)
+ {
+ return false;
+ }
+
if (millisecondsTimeout == 0)
{
// For 0-timeouts, we just return immediately.
return false;
}
- int spinCount = Threading.SpinWait.SpinCountforSpinBeforeWait;
+ int spinCount = Threading.SpinWait.SpinCountForSpinBeforeWait;
SpinWait spinner = default;
while (spinner.Count < spinCount)
{
@@ -4706,9 +4712,6 @@ internal void RemoveContinuation(object continuationObject) // could be TaskCont
[MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
public static void WaitAll(params Task[] tasks)
{
-#if TARGET_WASI
- if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
-#endif
if (tasks is null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks);
@@ -4734,9 +4737,6 @@ public static void WaitAll(params Task[] tasks)
[UnsupportedOSPlatform("browser")]
public static void WaitAll(params ReadOnlySpan tasks)
{
-#if TARGET_WASI
- if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
-#endif
bool waitResult = WaitAllCore(tasks, Timeout.Infinite, default);
Debug.Assert(waitResult, "expected wait to succeed");
}
@@ -4774,9 +4774,6 @@ public static void WaitAll(params ReadOnlySpan tasks)
[MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
public static bool WaitAll(Task[] tasks, TimeSpan timeout)
{
-#if TARGET_WASI
- if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
-#endif
long totalMilliseconds = (long)timeout.TotalMilliseconds;
if (totalMilliseconds is < -1 or > int.MaxValue)
{
@@ -4821,9 +4818,6 @@ public static bool WaitAll(Task[] tasks, TimeSpan timeout)
[MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
public static bool WaitAll(Task[] tasks, int millisecondsTimeout)
{
-#if TARGET_WASI
- if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
-#endif
if (tasks is null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks);
@@ -4858,9 +4852,6 @@ public static bool WaitAll(Task[] tasks, int millisecondsTimeout)
[MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
public static void WaitAll(Task[] tasks, CancellationToken cancellationToken)
{
-#if TARGET_WASI
- if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
-#endif
if (tasks is null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks);
@@ -4907,9 +4898,6 @@ public static void WaitAll(Task[] tasks, CancellationToken cancellationToken)
[MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
public static bool WaitAll(Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken)
{
-#if TARGET_WASI
- if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
-#endif
if (tasks is null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks);
@@ -4932,9 +4920,6 @@ public static bool WaitAll(Task[] tasks, int millisecondsTimeout, CancellationTo
[UnsupportedOSPlatform("browser")]
public static void WaitAll(IEnumerable tasks, CancellationToken cancellationToken = default)
{
-#if TARGET_WASI
- if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
-#endif
if (tasks is null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks);
@@ -4953,9 +4938,6 @@ public static void WaitAll(IEnumerable tasks, CancellationToken cancellati
[UnsupportedOSPlatform("browser")]
private static bool WaitAllCore(ReadOnlySpan tasks, int millisecondsTimeout, CancellationToken cancellationToken)
{
-#if TARGET_WASI
- if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
-#endif
if (millisecondsTimeout < -1)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.millisecondsTimeout);
@@ -5016,6 +4998,8 @@ private static bool WaitAllCore(ReadOnlySpan tasks, int millisecondsTimeou
if (waitedOnTaskList != null)
{
+ Thread.ThrowIfSingleThreaded();
+
// Block waiting for the tasks to complete.
returnValue = WaitAllBlockingCore(waitedOnTaskList, millisecondsTimeout, cancellationToken);
@@ -5086,12 +5070,11 @@ private static void AddToList(T item, ref List? list, int initSize)
[UnsupportedOSPlatform("browser")]
private static bool WaitAllBlockingCore(List tasks, int millisecondsTimeout, CancellationToken cancellationToken)
{
-#if TARGET_WASI
- if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
-#endif
Debug.Assert(tasks != null, "Expected a non-null list of tasks");
Debug.Assert(tasks.Count > 0, "Expected at least one task");
+ Thread.ThrowIfSingleThreaded();
+
bool waitCompleted = false;
var mres = new SetOnCountdownMres(tasks.Count);
try
@@ -5357,6 +5340,8 @@ private static int WaitAnyCore(Task[] tasks, int millisecondsTimeout, Cancellati
if (signaledTaskIndex == -1 && tasks.Length != 0)
{
+ Thread.ThrowIfSingleThreaded();
+
Task firstCompleted = TaskFactory.CommonCWAnyLogic(tasks, isSyncBlocking: true);
bool waitCompleted = firstCompleted.Wait(millisecondsTimeout, cancellationToken);
if (waitCompleted)
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskAsyncEnumerableExtensions.ToBlockingEnumerable.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskAsyncEnumerableExtensions.ToBlockingEnumerable.cs
index 2b66d88540b1b6..1c7ee620860a72 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskAsyncEnumerableExtensions.ToBlockingEnumerable.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskAsyncEnumerableExtensions.ToBlockingEnumerable.cs
@@ -26,9 +26,8 @@ public static partial class TaskAsyncEnumerableExtensions
[UnsupportedOSPlatform("browser")]
public static IEnumerable ToBlockingEnumerable(this IAsyncEnumerable source, CancellationToken cancellationToken = default)
{
-#if TARGET_WASI
- if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
-#endif
+ Thread.ThrowIfSingleThreaded();
+
IAsyncEnumerator enumerator = source.GetAsyncEnumerator(cancellationToken);
// A ManualResetEventSlim variant that lets us reuse the same
// awaiter callback allocation across the entire enumeration.
@@ -82,9 +81,8 @@ public ManualResetEventWithAwaiterSupport()
[UnsupportedOSPlatform("browser")]
public void Wait(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion
{
-#if TARGET_WASI
- if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
-#endif
+ Thread.ThrowIfSingleThreaded();
+
awaiter.UnsafeOnCompleted(_onCompleted);
Wait();
Reset();
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ThreadPoolTaskScheduler.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ThreadPoolTaskScheduler.cs
index 6de92eea0af7de..d4edf231413265 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ThreadPoolTaskScheduler.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ThreadPoolTaskScheduler.cs
@@ -42,7 +42,7 @@ internal ThreadPoolTaskScheduler()
protected internal override void QueueTask(Task task)
{
TaskCreationOptions options = task.Options;
- if (Thread.IsThreadStartSupported && (options & TaskCreationOptions.LongRunning) != 0)
+ if (!Thread.IsSingleThreaded && (options & TaskCreationOptions.LongRunning) != 0)
{
// Run LongRunning tasks on their own dedicated thread.
new Thread(s_longRunningThreadWork)
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs
index b9dd510b80c02a..161889ce1bd082 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs
@@ -169,23 +169,6 @@ public Thread(ParameterizedThreadStart start, int maxStackSize)
Initialize();
}
-#if (!TARGET_BROWSER && !TARGET_WASI) || FEATURE_WASM_MANAGED_THREADS
- [UnsupportedOSPlatformGuard("browser")]
- [UnsupportedOSPlatformGuard("wasi")]
- internal static bool IsThreadStartSupported => true;
-#else
- [UnsupportedOSPlatformGuard("browser")]
- [UnsupportedOSPlatformGuard("wasi")]
- internal static bool IsThreadStartSupported => false;
-#endif
-
- internal static void ThrowIfNoThreadStart()
- {
- if (IsThreadStartSupported)
- return;
- throw new PlatformNotSupportedException();
- }
-
/// Causes the operating system to change the state of the current instance to , and optionally supplies an object containing data to be used by the method the thread executes.
/// An object that contains data to be used by the method the thread executes.
/// The thread has already been started.
@@ -212,10 +195,7 @@ internal static void ThrowIfNoThreadStart()
private void Start(object? parameter, bool captureContext)
{
-#if TARGET_WASI
- if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
-#endif
- ThrowIfNoThreadStart();
+ Thread.ThrowIfSingleThreaded();
StartHelper? startHelper = _startHelper;
@@ -258,7 +238,7 @@ private void Start(object? parameter, bool captureContext)
private void Start(bool captureContext)
{
- ThrowIfNoThreadStart();
+ Thread.ThrowIfSingleThreaded();
StartHelper? startHelper = _startHelper;
// In the case of a null startHelper (second call to start on same thread)
@@ -449,7 +429,7 @@ internal void SetThreadPoolWorkerThreadName()
internal void ResetThreadPoolThread()
{
Debug.Assert(this == CurrentThread);
- Debug.Assert(!IsThreadStartSupported || IsThreadPoolThread); // there are no dedicated threadpool threads on runtimes where we can't start threads
+ Debug.Assert(IsSingleThreaded || IsThreadPoolThread); // there are no dedicated threadpool threads on runtimes where we can't start threads
if (_mayNeedResetForThreadPool)
{
@@ -461,7 +441,7 @@ internal void ResetThreadPoolThread()
private void ResetThreadPoolThreadSlow()
{
Debug.Assert(this == CurrentThread);
- Debug.Assert(!IsThreadStartSupported || IsThreadPoolThread); // there are no dedicated threadpool threads on runtimes where we can't start threads
+ Debug.Assert(IsSingleThreaded || IsThreadPoolThread); // there are no dedicated threadpool threads on runtimes where we can't start threads
Debug.Assert(_mayNeedResetForThreadPool);
_mayNeedResetForThreadPool = false;
@@ -757,6 +737,27 @@ public static int GetCurrentProcessorId()
return ProcessorIdCache.GetCurrentProcessorId();
}
+ [SupportedOSPlatformGuard("browser")]
+ [SupportedOSPlatformGuard("wasi")]
+#if FEATURE_SINGLE_THREADED
+ internal static bool IsSingleThreaded => true;
+ [DoesNotReturn]
+ internal static void ThrowIfSingleThreaded()
+ {
+ throw new PlatformNotSupportedException();
+ }
+#else
+ internal static bool IsSingleThreaded => false;
+#if FEATURE_WASM_MANAGED_THREADS
+ internal static void ThrowIfSingleThreaded()
+ {
+ AssureBlockingPossible();
+ }
+#else
+ internal static void ThrowIfSingleThreaded() { }
+#endif
+#endif
+
#if FEATURE_WASM_MANAGED_THREADS
[ThreadStatic]
public static bool ThrowOnBlockingWaitOnJSInteropThread;
@@ -800,6 +801,10 @@ public static void ForceBlockingWait(Action