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(); } }