Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit c7fda3b

Browse files
authored
Add Thread.UnsafeStart (#47056)
1 parent 3f23874 commit c7fda3b

File tree

9 files changed

+127
-55
lines changed

9 files changed

+127
-55
lines changed

eng/CodeAnalysis.ruleset

+1-1
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,7 @@
479479
<Rule Id="IDE0076" Action="Hidden" /> <!-- InvalidSuppressMessageAttribute -->
480480
<Rule Id="IDE0077" Action="Hidden" /> <!-- LegacyFormatSuppressMessageAttribute -->
481481
<Rule Id="IDE0078" Action="Hidden" /> <!-- UsePatternCombinators -->
482-
<Rule Id="IDE0079" Action="Warning" /> <!-- RemoveUnnecessarySuppression -->
482+
<Rule Id="IDE0079" Action="Hidden" /> <!-- RemoveUnnecessarySuppression -->
483483
<Rule Id="IDE0080" Action="Hidden" /> <!-- RemoveConfusingSuppressionForIsExpression -->
484484
<Rule Id="IDE0081" Action="Hidden" /> <!-- RemoveUnnecessaryByVal -->
485485
<Rule Id="IDE0082" Action="Warning" /> <!-- ConvertTypeOfToNameOf -->

src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.OSX.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -199,12 +199,12 @@ public static void ScheduleEventStream(SafeEventStreamHandle eventStream)
199199
Debug.Assert(s_scheduledStreamsCount == 0);
200200
s_scheduledStreamsCount = 1;
201201
var runLoopStarted = new ManualResetEventSlim();
202-
new Thread(args =>
202+
new Thread(static args =>
203203
{
204204
object[] inputArgs = (object[])args!;
205205
WatchForFileSystemEventsThreadStart((ManualResetEventSlim)inputArgs[0], (SafeEventStreamHandle)inputArgs[1]);
206206
})
207-
{ IsBackground = true }.Start(new object[] { runLoopStarted, eventStream });
207+
{ IsBackground = true }.UnsafeStart(new object[] { runLoopStarted, eventStream });
208208

209209
runLoopStarted.Wait();
210210
}

src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs

+4-14
Original file line numberDiff line numberDiff line change
@@ -157,20 +157,10 @@ private SocketAsyncEngine()
157157
throw new InternalException(err);
158158
}
159159

160-
bool suppressFlow = !ExecutionContext.IsFlowSuppressed();
161-
try
162-
{
163-
if (suppressFlow) ExecutionContext.SuppressFlow();
164-
165-
Thread thread = new Thread(s => ((SocketAsyncEngine)s!).EventLoop());
166-
thread.IsBackground = true;
167-
thread.Name = ".NET Sockets";
168-
thread.Start(this);
169-
}
170-
finally
171-
{
172-
if (suppressFlow) ExecutionContext.RestoreFlow();
173-
}
160+
var thread = new Thread(static s => ((SocketAsyncEngine)s!).EventLoop());
161+
thread.IsBackground = true;
162+
thread.Name = ".NET Sockets";
163+
thread.UnsafeStart(this);
174164
}
175165
catch
176166
{

src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/CounterGroup.cs

+8-1
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ private void EnableTimer(float pollingIntervalInSeconds)
133133
ResetCounters(); // Reset statistics for counters before we start the thread.
134134

135135
_timeStampSinceCollectionStarted = DateTime.UtcNow;
136+
#if ES_BUILD_STANDALONE
136137
// Don't capture the current ExecutionContext and its AsyncLocals onto the timer causing them to live forever
137138
bool restoreFlow = false;
138139
try
@@ -142,7 +143,7 @@ private void EnableTimer(float pollingIntervalInSeconds)
142143
ExecutionContext.SuppressFlow();
143144
restoreFlow = true;
144145
}
145-
146+
#endif
146147
_nextPollingTimeStamp = DateTime.UtcNow + new TimeSpan(0, 0, (int)pollingIntervalInSeconds);
147148

148149
// Create the polling thread and init all the shared state if needed
@@ -151,7 +152,11 @@ private void EnableTimer(float pollingIntervalInSeconds)
151152
s_pollingThreadSleepEvent = new AutoResetEvent(false);
152153
s_counterGroupEnabledList = new List<CounterGroup>();
153154
s_pollingThread = new Thread(PollForValues) { IsBackground = true };
155+
#if ES_BUILD_STANDALONE
154156
s_pollingThread.Start();
157+
#else
158+
s_pollingThread.UnsafeStart();
159+
#endif
155160
}
156161

157162
if (!s_counterGroupEnabledList!.Contains(this))
@@ -162,13 +167,15 @@ private void EnableTimer(float pollingIntervalInSeconds)
162167
// notify the polling thread that the polling interval may have changed and the sleep should
163168
// be recomputed
164169
s_pollingThreadSleepEvent!.Set();
170+
#if ES_BUILD_STANDALONE
165171
}
166172
finally
167173
{
168174
// Restore the current ExecutionContext
169175
if (restoreFlow)
170176
ExecutionContext.RestoreFlow();
171177
}
178+
#endif
172179
}
173180
}
174181

src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ThreadPoolTaskScheduler.cs

+4-7
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,14 @@ protected internal override void QueueTask(Task task)
4545
if (Thread.IsThreadStartSupported && (options & TaskCreationOptions.LongRunning) != 0)
4646
{
4747
// Run LongRunning tasks on their own dedicated thread.
48-
Thread thread = new Thread(s_longRunningThreadWork);
49-
thread.IsBackground = true; // Keep this thread from blocking process shutdown
50-
#if !TARGET_BROWSER
51-
thread.Start(task);
52-
#endif
48+
#pragma warning disable CA1416 // TODO: https://github.com/dotnet/runtime/issues/44922
49+
new Thread(s_longRunningThreadWork) { IsBackground = true }.UnsafeStart(task);
50+
#pragma warning restore CA1416
5351
}
5452
else
5553
{
5654
// Normal handling for non-LongRunning tasks.
57-
bool preferLocal = ((options & TaskCreationOptions.PreferFairness) == 0);
58-
ThreadPool.UnsafeQueueUserWorkItemInternal(task, preferLocal);
55+
ThreadPool.UnsafeQueueUserWorkItemInternal(task, (options & TaskCreationOptions.PreferFairness) == 0);
5956
}
6057
}
6158

src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs

+38-13
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,27 @@ public Thread(ParameterizedThreadStart start, int maxStackSize)
153153
#if !TARGET_BROWSER
154154
internal const bool IsThreadStartSupported = true;
155155

156+
/// <summary>Causes the operating system to change the state of the current instance to <see cref="ThreadState.Running"/>, and optionally supplies an object containing data to be used by the method the thread executes.</summary>
157+
/// <param name="parameter">An object that contains data to be used by the method the thread executes.</param>
158+
/// <exception cref="ThreadStateException">The thread has already been started.</exception>
159+
/// <exception cref="OutOfMemoryException">There is not enough memory available to start this thread.</exception>
160+
/// <exception cref="InvalidOperationException">This thread was created using a <see cref="ThreadStart"/> delegate instead of a <see cref="ParameterizedThreadStart"/> delegate.</exception>
156161
[UnsupportedOSPlatform("browser")]
157-
public void Start(object? parameter)
162+
public void Start(object? parameter) => Start(parameter, captureContext: true);
163+
164+
/// <summary>Causes the operating system to change the state of the current instance to <see cref="ThreadState.Running"/>, and optionally supplies an object containing data to be used by the method the thread executes.</summary>
165+
/// <param name="parameter">An object that contains data to be used by the method the thread executes.</param>
166+
/// <exception cref="ThreadStateException">The thread has already been started.</exception>
167+
/// <exception cref="OutOfMemoryException">There is not enough memory available to start this thread.</exception>
168+
/// <exception cref="InvalidOperationException">This thread was created using a <see cref="ThreadStart"/> delegate instead of a <see cref="ParameterizedThreadStart"/> delegate.</exception>
169+
/// <remarks>
170+
/// Unlike <see cref="Start"/>, which captures the current <see cref="ExecutionContext"/> and uses that context to invoke the thread's delegate,
171+
/// <see cref="UnsafeStart"/> explicitly avoids capturing the current context and flowing it to the invocation.
172+
/// </remarks>
173+
[UnsupportedOSPlatform("browser")]
174+
public void UnsafeStart(object? parameter) => Start(parameter, captureContext: false);
175+
176+
private void Start(object? parameter, bool captureContext)
158177
{
159178
StartHelper? startHelper = _startHelper;
160179

@@ -169,14 +188,29 @@ public void Start(object? parameter)
169188
}
170189

171190
startHelper._startArg = parameter;
172-
startHelper._executionContext = ExecutionContext.Capture();
191+
startHelper._executionContext = captureContext ? ExecutionContext.Capture() : null;
173192
}
174193

175194
StartCore();
176195
}
177196

197+
/// <summary>Causes the operating system to change the state of the current instance to <see cref="ThreadState.Running"/>.</summary>
198+
/// <exception cref="ThreadStateException">The thread has already been started.</exception>
199+
/// <exception cref="OutOfMemoryException">There is not enough memory available to start this thread.</exception>
200+
[UnsupportedOSPlatform("browser")]
201+
public void Start() => Start(captureContext: true);
202+
203+
/// <summary>Causes the operating system to change the state of the current instance to <see cref="ThreadState.Running"/>.</summary>
204+
/// <exception cref="ThreadStateException">The thread has already been started.</exception>
205+
/// <exception cref="OutOfMemoryException">There is not enough memory available to start this thread.</exception>
206+
/// <remarks>
207+
/// Unlike <see cref="Start"/>, which captures the current <see cref="ExecutionContext"/> and uses that context to invoke the thread's delegate,
208+
/// <see cref="UnsafeStart"/> explicitly avoids capturing the current context and flowing it to the invocation.
209+
/// </remarks>
178210
[UnsupportedOSPlatform("browser")]
179-
public void Start()
211+
public void UnsafeStart() => Start(captureContext: false);
212+
213+
private void Start(bool captureContext)
180214
{
181215
StartHelper? startHelper = _startHelper;
182216

@@ -185,20 +219,11 @@ public void Start()
185219
if (startHelper != null)
186220
{
187221
startHelper._startArg = null;
188-
startHelper._executionContext = ExecutionContext.Capture();
222+
startHelper._executionContext = captureContext ? ExecutionContext.Capture() : null;
189223
}
190224

191225
StartCore();
192226
}
193-
194-
internal void UnsafeStart()
195-
{
196-
Debug.Assert(_startHelper != null);
197-
Debug.Assert(_startHelper._startArg == null);
198-
Debug.Assert(_startHelper._executionContext == null);
199-
200-
StartCore();
201-
}
202227
#endif
203228

204229
private void RequireCurrentThread()

src/libraries/System.Threading.Thread/ref/System.Threading.Thread.cs

+4
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,10 @@ public void Start(object? parameter) { }
9696
[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)]
9797
public void Suspend() { }
9898
public bool TrySetApartmentState(System.Threading.ApartmentState state) { throw null; }
99+
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
100+
public void UnsafeStart() { }
101+
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
102+
public void UnsafeStart(object? parameter) { }
99103
public static byte VolatileRead(ref byte address) { throw null; }
100104
public static double VolatileRead(ref double address) { throw null; }
101105
public static short VolatileRead(ref short address) { throw null; }

src/libraries/System.Threading.Thread/tests/ThreadTests.cs

+60-17
Original file line numberDiff line numberDiff line change
@@ -1034,9 +1034,35 @@ public static void SleepTest()
10341034
Assert.InRange((int)stopwatch.ElapsedMilliseconds, 100, int.MaxValue);
10351035
}
10361036

1037-
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
1038-
public static void StartTest()
1037+
[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
1038+
[InlineData(false)]
1039+
[InlineData(true)]
1040+
public static void StartTest(bool useUnsafeStart)
10391041
{
1042+
void Start(Thread t)
1043+
{
1044+
if (useUnsafeStart)
1045+
{
1046+
t.UnsafeStart();
1047+
}
1048+
else
1049+
{
1050+
t.Start();
1051+
}
1052+
}
1053+
1054+
void StartParameter(Thread t, object parameter)
1055+
{
1056+
if (useUnsafeStart)
1057+
{
1058+
t.UnsafeStart(parameter);
1059+
}
1060+
else
1061+
{
1062+
t.Start(parameter);
1063+
}
1064+
}
1065+
10401066
var e = new AutoResetEvent(false);
10411067
Action waitForThread;
10421068
Thread t = null;
@@ -1046,33 +1072,33 @@ public static void StartTest()
10461072
Assert.Same(t, Thread.CurrentThread);
10471073
});
10481074
t.IsBackground = true;
1049-
Assert.Throws<InvalidOperationException>(() => t.Start(null));
1050-
Assert.Throws<InvalidOperationException>(() => t.Start(t));
1051-
t.Start();
1052-
Assert.Throws<ThreadStateException>(() => t.Start());
1075+
Assert.Throws<InvalidOperationException>(() => StartParameter(t, null));
1076+
Assert.Throws<InvalidOperationException>(() => StartParameter(t, t));
1077+
Start(t);
1078+
Assert.Throws<ThreadStateException>(() => Start(t));
10531079
e.Set();
10541080
waitForThread();
1055-
Assert.Throws<ThreadStateException>(() => t.Start());
1081+
Assert.Throws<ThreadStateException>(() => Start(t));
10561082

10571083
t = ThreadTestHelpers.CreateGuardedThread(out waitForThread, parameter => e.CheckedWait());
10581084
t.IsBackground = true;
1059-
t.Start();
1060-
Assert.Throws<ThreadStateException>(() => t.Start());
1061-
Assert.Throws<ThreadStateException>(() => t.Start(null));
1062-
Assert.Throws<ThreadStateException>(() => t.Start(t));
1085+
Start(t);
1086+
Assert.Throws<ThreadStateException>(() => Start(t));
1087+
Assert.Throws<ThreadStateException>(() => StartParameter(t, null));
1088+
Assert.Throws<ThreadStateException>(() => StartParameter(t, t));
10631089
e.Set();
10641090
waitForThread();
1065-
Assert.Throws<ThreadStateException>(() => t.Start());
1066-
Assert.Throws<ThreadStateException>(() => t.Start(null));
1067-
Assert.Throws<ThreadStateException>(() => t.Start(t));
1091+
Assert.Throws<ThreadStateException>(() => Start(t));
1092+
Assert.Throws<ThreadStateException>(() => StartParameter(t, null));
1093+
Assert.Throws<ThreadStateException>(() => StartParameter(t, t));
10681094

10691095
t = ThreadTestHelpers.CreateGuardedThread(out waitForThread, parameter =>
10701096
{
10711097
Assert.Null(parameter);
10721098
Assert.Same(t, Thread.CurrentThread);
10731099
});
10741100
t.IsBackground = true;
1075-
t.Start();
1101+
Start(t);
10761102
waitForThread();
10771103

10781104
t = ThreadTestHelpers.CreateGuardedThread(out waitForThread, parameter =>
@@ -1081,7 +1107,7 @@ public static void StartTest()
10811107
Assert.Same(t, Thread.CurrentThread);
10821108
});
10831109
t.IsBackground = true;
1084-
t.Start(null);
1110+
StartParameter(t, null);
10851111
waitForThread();
10861112

10871113
t = ThreadTestHelpers.CreateGuardedThread(out waitForThread, parameter =>
@@ -1090,7 +1116,24 @@ public static void StartTest()
10901116
Assert.Same(t, Thread.CurrentThread);
10911117
});
10921118
t.IsBackground = true;
1093-
t.Start(t);
1119+
StartParameter(t, t);
1120+
waitForThread();
1121+
1122+
var al = new AsyncLocal<int>();
1123+
al.Value = 42;
1124+
t = ThreadTestHelpers.CreateGuardedThread(out waitForThread, parameter =>
1125+
{
1126+
if (useUnsafeStart)
1127+
{
1128+
Assert.Equal(0, al.Value);
1129+
}
1130+
else
1131+
{
1132+
Assert.Equal(42, al.Value);
1133+
}
1134+
});
1135+
t.IsBackground = true;
1136+
StartParameter(t, t);
10941137
waitForThread();
10951138
}
10961139

src/mono/netcore/System.Private.CoreLib/src/System/Threading/Thread.Browser.Mono.cs

+6
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,11 @@ public partial class Thread
1414

1515
[UnsupportedOSPlatform("browser")]
1616
public void Start(object parameter) => throw new PlatformNotSupportedException();
17+
18+
[UnsupportedOSPlatform("browser")]
19+
public void UnsafeStart() => throw new PlatformNotSupportedException();
20+
21+
[UnsupportedOSPlatform("browser")]
22+
public void UnsafeStart(object parameter) => throw new PlatformNotSupportedException();
1723
}
1824
}

0 commit comments

Comments
 (0)