diff --git a/eng/CodeAnalysis.ruleset b/eng/CodeAnalysis.ruleset
index 6a6b3132557605..d5139f7a0e6f86 100644
--- a/eng/CodeAnalysis.ruleset
+++ b/eng/CodeAnalysis.ruleset
@@ -479,7 +479,7 @@
-
+
diff --git a/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.OSX.cs b/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.OSX.cs
index abd5c3b689b62b..4c159761063b7c 100644
--- a/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.OSX.cs
+++ b/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.OSX.cs
@@ -199,12 +199,12 @@ public static void ScheduleEventStream(SafeEventStreamHandle eventStream)
Debug.Assert(s_scheduledStreamsCount == 0);
s_scheduledStreamsCount = 1;
var runLoopStarted = new ManualResetEventSlim();
- new Thread(args =>
+ new Thread(static args =>
{
object[] inputArgs = (object[])args!;
WatchForFileSystemEventsThreadStart((ManualResetEventSlim)inputArgs[0], (SafeEventStreamHandle)inputArgs[1]);
})
- { IsBackground = true }.Start(new object[] { runLoopStarted, eventStream });
+ { IsBackground = true }.UnsafeStart(new object[] { runLoopStarted, eventStream });
runLoopStarted.Wait();
}
diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs
index 301fe9b5f60b22..d000e329ca5672 100644
--- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs
+++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs
@@ -157,20 +157,10 @@ private SocketAsyncEngine()
throw new InternalException(err);
}
- bool suppressFlow = !ExecutionContext.IsFlowSuppressed();
- try
- {
- if (suppressFlow) ExecutionContext.SuppressFlow();
-
- Thread thread = new Thread(s => ((SocketAsyncEngine)s!).EventLoop());
- thread.IsBackground = true;
- thread.Name = ".NET Sockets";
- thread.Start(this);
- }
- finally
- {
- if (suppressFlow) ExecutionContext.RestoreFlow();
- }
+ var thread = new Thread(static s => ((SocketAsyncEngine)s!).EventLoop());
+ thread.IsBackground = true;
+ thread.Name = ".NET Sockets";
+ thread.UnsafeStart(this);
}
catch
{
diff --git a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/CounterGroup.cs b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/CounterGroup.cs
index e6ce6fda4a38a6..f6c398887d1a02 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/CounterGroup.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/CounterGroup.cs
@@ -133,6 +133,7 @@ private void EnableTimer(float pollingIntervalInSeconds)
ResetCounters(); // Reset statistics for counters before we start the thread.
_timeStampSinceCollectionStarted = DateTime.UtcNow;
+#if ES_BUILD_STANDALONE
// Don't capture the current ExecutionContext and its AsyncLocals onto the timer causing them to live forever
bool restoreFlow = false;
try
@@ -142,7 +143,7 @@ private void EnableTimer(float pollingIntervalInSeconds)
ExecutionContext.SuppressFlow();
restoreFlow = true;
}
-
+#endif
_nextPollingTimeStamp = DateTime.UtcNow + new TimeSpan(0, 0, (int)pollingIntervalInSeconds);
// Create the polling thread and init all the shared state if needed
@@ -151,7 +152,11 @@ private void EnableTimer(float pollingIntervalInSeconds)
s_pollingThreadSleepEvent = new AutoResetEvent(false);
s_counterGroupEnabledList = new List();
s_pollingThread = new Thread(PollForValues) { IsBackground = true };
+#if ES_BUILD_STANDALONE
s_pollingThread.Start();
+#else
+ s_pollingThread.UnsafeStart();
+#endif
}
if (!s_counterGroupEnabledList!.Contains(this))
@@ -162,6 +167,7 @@ private void EnableTimer(float pollingIntervalInSeconds)
// notify the polling thread that the polling interval may have changed and the sleep should
// be recomputed
s_pollingThreadSleepEvent!.Set();
+#if ES_BUILD_STANDALONE
}
finally
{
@@ -169,6 +175,7 @@ private void EnableTimer(float pollingIntervalInSeconds)
if (restoreFlow)
ExecutionContext.RestoreFlow();
}
+#endif
}
}
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 dbe39bf4e71fb0..760dfb4c33c838 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
@@ -45,17 +45,14 @@ protected internal override void QueueTask(Task task)
if (Thread.IsThreadStartSupported && (options & TaskCreationOptions.LongRunning) != 0)
{
// Run LongRunning tasks on their own dedicated thread.
- Thread thread = new Thread(s_longRunningThreadWork);
- thread.IsBackground = true; // Keep this thread from blocking process shutdown
-#if !TARGET_BROWSER
- thread.Start(task);
-#endif
+#pragma warning disable CA1416 // TODO: https://github.com/dotnet/runtime/issues/44922
+ new Thread(s_longRunningThreadWork) { IsBackground = true }.UnsafeStart(task);
+#pragma warning restore CA1416
}
else
{
// Normal handling for non-LongRunning tasks.
- bool preferLocal = ((options & TaskCreationOptions.PreferFairness) == 0);
- ThreadPool.UnsafeQueueUserWorkItemInternal(task, preferLocal);
+ ThreadPool.UnsafeQueueUserWorkItemInternal(task, (options & TaskCreationOptions.PreferFairness) == 0);
}
}
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 96048ecdf4837b..27cfb6ca12ad15 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs
@@ -153,8 +153,27 @@ public Thread(ParameterizedThreadStart start, int maxStackSize)
#if !TARGET_BROWSER
internal const bool IsThreadStartSupported = true;
+ /// 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.
+ /// There is not enough memory available to start this thread.
+ /// This thread was created using a delegate instead of a delegate.
[UnsupportedOSPlatform("browser")]
- public void Start(object? parameter)
+ public void Start(object? parameter) => Start(parameter, captureContext: true);
+
+ /// 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.
+ /// There is not enough memory available to start this thread.
+ /// This thread was created using a delegate instead of a delegate.
+ ///
+ /// Unlike , which captures the current and uses that context to invoke the thread's delegate,
+ /// explicitly avoids capturing the current context and flowing it to the invocation.
+ ///
+ [UnsupportedOSPlatform("browser")]
+ public void UnsafeStart(object? parameter) => Start(parameter, captureContext: false);
+
+ private void Start(object? parameter, bool captureContext)
{
StartHelper? startHelper = _startHelper;
@@ -169,14 +188,29 @@ public void Start(object? parameter)
}
startHelper._startArg = parameter;
- startHelper._executionContext = ExecutionContext.Capture();
+ startHelper._executionContext = captureContext ? ExecutionContext.Capture() : null;
}
StartCore();
}
+ /// Causes the operating system to change the state of the current instance to .
+ /// The thread has already been started.
+ /// There is not enough memory available to start this thread.
+ [UnsupportedOSPlatform("browser")]
+ public void Start() => Start(captureContext: true);
+
+ /// Causes the operating system to change the state of the current instance to .
+ /// The thread has already been started.
+ /// There is not enough memory available to start this thread.
+ ///
+ /// Unlike , which captures the current and uses that context to invoke the thread's delegate,
+ /// explicitly avoids capturing the current context and flowing it to the invocation.
+ ///
[UnsupportedOSPlatform("browser")]
- public void Start()
+ public void UnsafeStart() => Start(captureContext: false);
+
+ private void Start(bool captureContext)
{
StartHelper? startHelper = _startHelper;
@@ -185,20 +219,11 @@ public void Start()
if (startHelper != null)
{
startHelper._startArg = null;
- startHelper._executionContext = ExecutionContext.Capture();
+ startHelper._executionContext = captureContext ? ExecutionContext.Capture() : null;
}
StartCore();
}
-
- internal void UnsafeStart()
- {
- Debug.Assert(_startHelper != null);
- Debug.Assert(_startHelper._startArg == null);
- Debug.Assert(_startHelper._executionContext == null);
-
- StartCore();
- }
#endif
private void RequireCurrentThread()
diff --git a/src/libraries/System.Threading.Thread/ref/System.Threading.Thread.cs b/src/libraries/System.Threading.Thread/ref/System.Threading.Thread.cs
index 78d5954173f6c5..49a0c97d2d84bc 100644
--- a/src/libraries/System.Threading.Thread/ref/System.Threading.Thread.cs
+++ b/src/libraries/System.Threading.Thread/ref/System.Threading.Thread.cs
@@ -96,6 +96,10 @@ public void Start(object? parameter) { }
[System.ObsoleteAttribute("Thread.Suspend has been deprecated. Please use other classes in System.Threading, such as Monitor, Mutex, Event, and Semaphore, to synchronize Threads or protect resources. https://go.microsoft.com/fwlink/?linkid=14202", false)]
public void Suspend() { }
public bool TrySetApartmentState(System.Threading.ApartmentState state) { throw null; }
+ [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
+ public void UnsafeStart() { }
+ [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
+ public void UnsafeStart(object? parameter) { }
public static byte VolatileRead(ref byte address) { throw null; }
public static double VolatileRead(ref double address) { throw null; }
public static short VolatileRead(ref short address) { throw null; }
diff --git a/src/libraries/System.Threading.Thread/tests/ThreadTests.cs b/src/libraries/System.Threading.Thread/tests/ThreadTests.cs
index 877c2cb262db68..c982b5b15d3efa 100644
--- a/src/libraries/System.Threading.Thread/tests/ThreadTests.cs
+++ b/src/libraries/System.Threading.Thread/tests/ThreadTests.cs
@@ -1034,9 +1034,35 @@ public static void SleepTest()
Assert.InRange((int)stopwatch.ElapsedMilliseconds, 100, int.MaxValue);
}
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
- public static void StartTest()
+ [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [InlineData(false)]
+ [InlineData(true)]
+ public static void StartTest(bool useUnsafeStart)
{
+ void Start(Thread t)
+ {
+ if (useUnsafeStart)
+ {
+ t.UnsafeStart();
+ }
+ else
+ {
+ t.Start();
+ }
+ }
+
+ void StartParameter(Thread t, object parameter)
+ {
+ if (useUnsafeStart)
+ {
+ t.UnsafeStart(parameter);
+ }
+ else
+ {
+ t.Start(parameter);
+ }
+ }
+
var e = new AutoResetEvent(false);
Action waitForThread;
Thread t = null;
@@ -1046,25 +1072,25 @@ public static void StartTest()
Assert.Same(t, Thread.CurrentThread);
});
t.IsBackground = true;
- Assert.Throws(() => t.Start(null));
- Assert.Throws(() => t.Start(t));
- t.Start();
- Assert.Throws(() => t.Start());
+ Assert.Throws(() => StartParameter(t, null));
+ Assert.Throws(() => StartParameter(t, t));
+ Start(t);
+ Assert.Throws(() => Start(t));
e.Set();
waitForThread();
- Assert.Throws(() => t.Start());
+ Assert.Throws(() => Start(t));
t = ThreadTestHelpers.CreateGuardedThread(out waitForThread, parameter => e.CheckedWait());
t.IsBackground = true;
- t.Start();
- Assert.Throws(() => t.Start());
- Assert.Throws(() => t.Start(null));
- Assert.Throws(() => t.Start(t));
+ Start(t);
+ Assert.Throws(() => Start(t));
+ Assert.Throws(() => StartParameter(t, null));
+ Assert.Throws(() => StartParameter(t, t));
e.Set();
waitForThread();
- Assert.Throws(() => t.Start());
- Assert.Throws(() => t.Start(null));
- Assert.Throws(() => t.Start(t));
+ Assert.Throws(() => Start(t));
+ Assert.Throws(() => StartParameter(t, null));
+ Assert.Throws(() => StartParameter(t, t));
t = ThreadTestHelpers.CreateGuardedThread(out waitForThread, parameter =>
{
@@ -1072,7 +1098,7 @@ public static void StartTest()
Assert.Same(t, Thread.CurrentThread);
});
t.IsBackground = true;
- t.Start();
+ Start(t);
waitForThread();
t = ThreadTestHelpers.CreateGuardedThread(out waitForThread, parameter =>
@@ -1081,7 +1107,7 @@ public static void StartTest()
Assert.Same(t, Thread.CurrentThread);
});
t.IsBackground = true;
- t.Start(null);
+ StartParameter(t, null);
waitForThread();
t = ThreadTestHelpers.CreateGuardedThread(out waitForThread, parameter =>
@@ -1090,7 +1116,24 @@ public static void StartTest()
Assert.Same(t, Thread.CurrentThread);
});
t.IsBackground = true;
- t.Start(t);
+ StartParameter(t, t);
+ waitForThread();
+
+ var al = new AsyncLocal();
+ al.Value = 42;
+ t = ThreadTestHelpers.CreateGuardedThread(out waitForThread, parameter =>
+ {
+ if (useUnsafeStart)
+ {
+ Assert.Equal(0, al.Value);
+ }
+ else
+ {
+ Assert.Equal(42, al.Value);
+ }
+ });
+ t.IsBackground = true;
+ StartParameter(t, t);
waitForThread();
}
diff --git a/src/mono/netcore/System.Private.CoreLib/src/System/Threading/Thread.Browser.Mono.cs b/src/mono/netcore/System.Private.CoreLib/src/System/Threading/Thread.Browser.Mono.cs
index 80513f2007eaa8..a456c5d8207ada 100644
--- a/src/mono/netcore/System.Private.CoreLib/src/System/Threading/Thread.Browser.Mono.cs
+++ b/src/mono/netcore/System.Private.CoreLib/src/System/Threading/Thread.Browser.Mono.cs
@@ -14,5 +14,11 @@ public partial class Thread
[UnsupportedOSPlatform("browser")]
public void Start(object parameter) => throw new PlatformNotSupportedException();
+
+ [UnsupportedOSPlatform("browser")]
+ public void UnsafeStart() => throw new PlatformNotSupportedException();
+
+ [UnsupportedOSPlatform("browser")]
+ public void UnsafeStart(object parameter) => throw new PlatformNotSupportedException();
}
}