Skip to content

Commit 3990bf7

Browse files
committed
Use ExecutionContext.Restore rather than EC.Run callback
1 parent 4aea0a1 commit 3990bf7

File tree

2 files changed

+88
-82
lines changed

2 files changed

+88
-82
lines changed

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

Lines changed: 7 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -200,74 +200,27 @@ internal static void RunInternal(ExecutionContext? executionContext, ContextCall
200200
edi?.Throw();
201201
}
202202

203-
// Direct copy of the above RunInternal overload, except that it passes the state into the callback strongly-typed and by ref.
204-
internal static void RunInternal<TState>(ExecutionContext? executionContext, ContextCallback<TState> callback, ref TState state)
203+
internal static void Restore(ExecutionContext? executionContext)
205204
{
206-
// Note: ExecutionContext.RunInternal is an extremely hot function and used by every await, ThreadPool execution, etc.
207-
// Note: Manual enregistering may be addressed by "Exception Handling Write Through Optimization"
208-
// https://github.com/dotnet/runtime/blob/master/docs/design/features/eh-writethru.md
205+
Thread currentThread = Thread.CurrentThread;
209206

210-
// Enregister variables with 0 post-fix so they can be used in registers without EH forcing them to stack
211-
// Capture references to Thread Contexts
212-
Thread currentThread0 = Thread.CurrentThread;
213-
Thread currentThread = currentThread0;
214-
ExecutionContext? previousExecutionCtx0 = currentThread0._executionContext;
215-
if (previousExecutionCtx0 != null && previousExecutionCtx0.m_isDefault)
207+
ExecutionContext? currentExecutionCtx = currentThread._executionContext;
208+
if (currentExecutionCtx != null && currentExecutionCtx.m_isDefault)
216209
{
217210
// Default is a null ExecutionContext internally
218-
previousExecutionCtx0 = null;
211+
currentExecutionCtx = null;
219212
}
220213

221-
// Store current ExecutionContext and SynchronizationContext as "previousXxx".
222-
// This allows us to restore them and undo any Context changes made in callback.Invoke
223-
// so that they won't "leak" back into caller.
224-
// These variables will cross EH so be forced to stack
225-
ExecutionContext? previousExecutionCtx = previousExecutionCtx0;
226-
SynchronizationContext? previousSyncCtx = currentThread0._synchronizationContext;
227-
228214
if (executionContext != null && executionContext.m_isDefault)
229215
{
230216
// Default is a null ExecutionContext internally
231217
executionContext = null;
232218
}
233219

234-
if (previousExecutionCtx0 != executionContext)
220+
if (currentExecutionCtx != executionContext)
235221
{
236-
RestoreChangedContextToThread(currentThread0, executionContext, previousExecutionCtx0);
222+
RestoreChangedContextToThread(currentThread, executionContext, currentExecutionCtx);
237223
}
238-
239-
ExceptionDispatchInfo? edi = null;
240-
try
241-
{
242-
callback.Invoke(ref state);
243-
}
244-
catch (Exception ex)
245-
{
246-
// Note: we have a "catch" rather than a "finally" because we want
247-
// to stop the first pass of EH here. That way we can restore the previous
248-
// context before any of our callers' EH filters run.
249-
edi = ExceptionDispatchInfo.Capture(ex);
250-
}
251-
252-
// Re-enregistrer variables post EH with 1 post-fix so they can be used in registers rather than from stack
253-
SynchronizationContext? previousSyncCtx1 = previousSyncCtx;
254-
Thread currentThread1 = currentThread;
255-
// The common case is that these have not changed, so avoid the cost of a write barrier if not needed.
256-
if (currentThread1._synchronizationContext != previousSyncCtx1)
257-
{
258-
// Restore changed SynchronizationContext back to previous
259-
currentThread1._synchronizationContext = previousSyncCtx1;
260-
}
261-
262-
ExecutionContext? previousExecutionCtx1 = previousExecutionCtx;
263-
ExecutionContext? currentExecutionCtx1 = currentThread1._executionContext;
264-
if (currentExecutionCtx1 != previousExecutionCtx1)
265-
{
266-
RestoreChangedContextToThread(currentThread1, previousExecutionCtx1, currentExecutionCtx1);
267-
}
268-
269-
// If exception was thrown by callback, rethrow it now original contexts are restored
270-
edi?.Throw();
271224
}
272225

273226
internal static void RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, object state)

src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Sources/ManualResetValueTaskSourceCore.cs

Lines changed: 81 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -207,51 +207,104 @@ private void SignalCompletion()
207207
}
208208
_completed = true;
209209

210-
if (_continuation != null || Interlocked.CompareExchange(ref _continuation, ManualResetValueTaskSourceCoreShared.s_sentinel, null) != null)
210+
if (_continuation is null && Interlocked.CompareExchange(ref _continuation, ManualResetValueTaskSourceCoreShared.s_sentinel, null) is null)
211211
{
212-
if (_executionContext != null)
212+
return;
213+
}
214+
215+
if (_executionContext is null)
216+
{
217+
if (_capturedContext is null)
218+
{
219+
if (RunContinuationsAsynchronously)
220+
{
221+
ThreadPool.UnsafeQueueUserWorkItem(_continuation, _continuationState, preferLocal: true);
222+
}
223+
else
224+
{
225+
_continuation(_continuationState);
226+
}
227+
228+
return;
229+
}
230+
231+
InvokeSchedulerContinuation();
232+
return;
233+
}
234+
235+
ExecutionContext? currentContext = ExecutionContext.Capture();
236+
// Restore the captured ExecutionContext before executing anything.
237+
ExecutionContext.Restore(_executionContext);
238+
239+
if (_capturedContext is null)
240+
{
241+
if (RunContinuationsAsynchronously)
213242
{
214-
ExecutionContext.RunInternal(
215-
_executionContext,
216-
(ref ManualResetValueTaskSourceCore<TResult> s) => s.InvokeContinuation(),
217-
ref this);
243+
ThreadPool.QueueUserWorkItem(_continuation, _continuationState, preferLocal: true);
244+
// Restore the current ExecutionContext.
245+
ExecutionContext.Restore(currentContext);
218246
}
219247
else
220248
{
221-
InvokeContinuation();
249+
// Running inline may throw; capture the edi if it does as we changed the ExecutionContext,
250+
// so need to restore it back before propagating the throw.
251+
ExceptionDispatchInfo? edi = InvokeInlineContinuation();
252+
// Restore the current ExecutionContext.
253+
ExecutionContext.Restore(currentContext);
254+
// Now rethrow the exception; if there is one.
255+
edi?.Throw();
222256
}
257+
258+
return;
223259
}
260+
261+
InvokeSchedulerContinuation();
262+
// Restore the current ExecutionContext.
263+
ExecutionContext.Restore(currentContext);
224264
}
225265

226266
/// <summary>
227-
/// Invokes the continuation with the appropriate captured context / scheduler.
228-
/// This assumes that if <see cref="_executionContext"/> is not null we're already
267+
/// Invokes the continuation inline and captures any exception thrown.
268+
/// This assumes that if <see cref="_continuation"/> is not null we're already
229269
/// running within that <see cref="ExecutionContext"/>.
230270
/// </summary>
231-
private void InvokeContinuation()
271+
private ExceptionDispatchInfo? InvokeInlineContinuation()
232272
{
233273
Debug.Assert(_continuation != null);
274+
Debug.Assert(_capturedContext == null);
275+
Debug.Assert(!RunContinuationsAsynchronously);
234276

235-
switch (_capturedContext)
277+
ExceptionDispatchInfo? edi = null;
278+
SynchronizationContext? syncContext = SynchronizationContext.Current;
279+
try
236280
{
237-
case null:
238-
if (RunContinuationsAsynchronously)
239-
{
240-
if (_executionContext != null)
241-
{
242-
ThreadPool.QueueUserWorkItem(_continuation, _continuationState, preferLocal: true);
243-
}
244-
else
245-
{
246-
ThreadPool.UnsafeQueueUserWorkItem(_continuation, _continuationState, preferLocal: true);
247-
}
248-
}
249-
else
250-
{
251-
_continuation(_continuationState);
252-
}
253-
break;
281+
_continuation(_continuationState);
282+
}
283+
catch (Exception ex)
284+
{
285+
// Note: we have a "catch" rather than a "finally" because we want
286+
// to stop the first pass of EH here. That way we can restore the previous
287+
// context before any of our callers' EH filters run.
288+
edi = ExceptionDispatchInfo.Capture(ex);
289+
}
290+
291+
// Set sync context back to what it was prior to coming in
292+
SynchronizationContext.SetSynchronizationContext(syncContext);
293+
return edi;
294+
}
295+
296+
/// <summary>
297+
/// Invokes the continuation with the appropriate scheduler.
298+
/// This assumes that if <see cref="_continuation"/> is not null we're already
299+
/// running within that <see cref="ExecutionContext"/>.
300+
/// </summary>
301+
private void InvokeSchedulerContinuation()
302+
{
303+
Debug.Assert(_capturedContext != null);
304+
Debug.Assert(_continuation != null);
254305

306+
switch (_capturedContext)
307+
{
255308
case SynchronizationContext sc:
256309
sc.Post(s =>
257310
{

0 commit comments

Comments
 (0)