From: Ben Adams Date: Wed, 31 Jan 2018 15:24:29 +0000 (+0000) Subject: Reduce Execution Context Save+Restore (#15629) X-Git-Tag: accepted/tizen/unified/20190422.045933~3146 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=181680249af92aa45c8fb44a3ee865c7b61fd765;p=platform%2Fupstream%2Fcoreclr.git Reduce Execution Context Save+Restore (#15629) * Reduce Save+Restore for ExecutionContext * Use flag rather than comparison to static * Skip null check for pre-checked EC.Run * Feedback * Add static helper lookup for default context for TP * Add note for enregistering * Return to Default context when no values * Default + FlowSuppressed Context * Move AsyncMethodBuilder.Start to static non-generic * Feedback --- diff --git a/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems b/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems index ec576aa..bb1a284 100644 --- a/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems +++ b/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems @@ -363,6 +363,7 @@ + diff --git a/src/mscorlib/shared/System/Runtime/CompilerServices/AsyncMethodBuilder.cs b/src/mscorlib/shared/System/Runtime/CompilerServices/AsyncMethodBuilder.cs new file mode 100644 index 0000000..8fa5b54 --- /dev/null +++ b/src/mscorlib/shared/System/Runtime/CompilerServices/AsyncMethodBuilder.cs @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics; +using System.Threading; + +namespace System.Runtime.CompilerServices +{ + internal static partial class AsyncMethodBuilder + { + /// Initiates the builder's execution with the associated state machine. + /// Specifies the type of the state machine. + /// The state machine instance, passed by reference. + [DebuggerStepThrough] + public static void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine + { + if (stateMachine == null) // TStateMachines are generally non-nullable value types, so this check will be elided + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine); + } + + // enregistrer variables with 0 post-fix so they can be used in registers without EH forcing them to stack + // Capture references to Thread Contexts + Thread currentThread0 = Thread.CurrentThread; + Thread currentThread = currentThread0; + ExecutionContext previousExecutionCtx0 = currentThread0.ExecutionContext; + + // Store current ExecutionContext and SynchronizationContext as "previousXxx". + // This allows us to restore them and undo any Context changes made in stateMachine.MoveNext + // so that they won't "leak" out of the first await. + ExecutionContext previousExecutionCtx = previousExecutionCtx0; + SynchronizationContext previousSyncCtx = currentThread0.SynchronizationContext; + + try + { + stateMachine.MoveNext(); + } + finally + { + // Re-enregistrer variables post EH with 1 post-fix so they can be used in registers rather than from stack + SynchronizationContext previousSyncCtx1 = previousSyncCtx; + Thread currentThread1 = currentThread; + // The common case is that these have not changed, so avoid the cost of a write barrier if not needed. + if (previousSyncCtx1 != currentThread1.SynchronizationContext) + { + // Restore changed SynchronizationContext back to previous + currentThread1.SynchronizationContext = previousSyncCtx1; + } + + ExecutionContext previousExecutionCtx1 = previousExecutionCtx; + ExecutionContext currentExecutionCtx1 = currentThread1.ExecutionContext; + if (previousExecutionCtx1 != currentExecutionCtx1) + { + // Restore changed ExecutionContext back to previous + currentThread1.ExecutionContext = previousExecutionCtx1; + if ((currentExecutionCtx1 != null && currentExecutionCtx1.HasChangeNotifications) || + (previousExecutionCtx1 != null && previousExecutionCtx1.HasChangeNotifications)) + { + // There are change notifications; trigger any affected + ExecutionContext.OnValuesChanged(currentExecutionCtx1, previousExecutionCtx1); + } + } + } + } + } +} diff --git a/src/mscorlib/shared/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs b/src/mscorlib/shared/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs index b5ecd79..23da48e 100644 --- a/src/mscorlib/shared/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs +++ b/src/mscorlib/shared/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs @@ -38,8 +38,9 @@ namespace System.Runtime.CompilerServices /// Begins running the builder with the associated state machine. /// The type of the state machine. /// The state machine instance, passed by reference. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine => - _methodBuilder.Start(ref stateMachine); // will provide the right ExecutionContext semantics + AsyncMethodBuilder.Start(ref stateMachine); // will provide the right ExecutionContext semantics /// Associates the builder with the specified state machine. /// The state machine instance to associate with the builder. diff --git a/src/mscorlib/shared/System/Threading/AsyncLocal.cs b/src/mscorlib/shared/System/Threading/AsyncLocal.cs index 59c8fb3..3531265 100644 --- a/src/mscorlib/shared/System/Threading/AsyncLocal.cs +++ b/src/mscorlib/shared/System/Threading/AsyncLocal.cs @@ -125,7 +125,7 @@ namespace System.Threading public static IAsyncLocalValueMap Empty { get; } = new EmptyAsyncLocalValueMap(); // Instance without any key/value pairs. Used as a singleton/ - private sealed class EmptyAsyncLocalValueMap : IAsyncLocalValueMap + internal sealed class EmptyAsyncLocalValueMap : IAsyncLocalValueMap { public IAsyncLocalValueMap Set(IAsyncLocal key, object value) { @@ -144,7 +144,7 @@ namespace System.Threading } // Instance with one key/value pair. - private sealed class OneElementAsyncLocalValueMap : IAsyncLocalValueMap + internal sealed class OneElementAsyncLocalValueMap : IAsyncLocalValueMap { private readonly IAsyncLocal _key1; private readonly object _value1; diff --git a/src/mscorlib/shared/System/Threading/ExecutionContext.cs b/src/mscorlib/shared/System/Threading/ExecutionContext.cs index 2d5f5be..9f27c6d 100644 --- a/src/mscorlib/shared/System/Threading/ExecutionContext.cs +++ b/src/mscorlib/shared/System/Threading/ExecutionContext.cs @@ -21,40 +21,18 @@ namespace System.Threading { public delegate void ContextCallback(Object state); - internal struct ExecutionContextSwitcher - { - internal ExecutionContext m_ec; - internal SynchronizationContext m_sc; - - internal void Undo(Thread currentThread) - { - Debug.Assert(currentThread == Thread.CurrentThread); - - // The common case is that these have not changed, so avoid the cost of a write if not needed. - if (currentThread.SynchronizationContext != m_sc) - { - currentThread.SynchronizationContext = m_sc; - } - - if (currentThread.ExecutionContext != m_ec) - { - ExecutionContext.Restore(currentThread, m_ec); - } - } - } - public sealed class ExecutionContext : IDisposable, ISerializable { - internal static readonly ExecutionContext Default = new ExecutionContext(); + internal static readonly ExecutionContext Default = new ExecutionContext(isDefault: true); private readonly IAsyncLocalValueMap m_localValues; private readonly IAsyncLocal[] m_localChangeNotifications; private readonly bool m_isFlowSuppressed; + private readonly bool m_isDefault; - private ExecutionContext() + private ExecutionContext(bool isDefault) { - m_localValues = AsyncLocalValueMap.Empty; - m_localChangeNotifications = Array.Empty(); + m_isDefault = isDefault; } private ExecutionContext( @@ -86,12 +64,14 @@ namespace System.Threading Debug.Assert(isFlowSuppressed != m_isFlowSuppressed); if (!isFlowSuppressed && - m_localValues == Default.m_localValues && - m_localChangeNotifications == Default.m_localChangeNotifications) + (m_localValues == null || + m_localValues.GetType() == typeof(AsyncLocalValueMap.EmptyAsyncLocalValueMap)) + ) { return null; // implies the default context } - return new ExecutionContext(m_localValues, m_localChangeNotifications, isFlowSuppressed); + // Flow suppressing a Default context will have null values, set them to Empty + return new ExecutionContext(m_localValues ?? AsyncLocalValueMap.Empty, m_localChangeNotifications ?? Array.Empty(), isFlowSuppressed); } public static AsyncFlowControl SuppressFlow() @@ -128,134 +108,248 @@ namespace System.Threading return executionContext != null && executionContext.m_isFlowSuppressed; } + internal bool HasChangeNotifications => m_localChangeNotifications != null; + + internal bool IsDefault => m_isDefault; + public static void Run(ExecutionContext executionContext, ContextCallback callback, Object state) { + // Note: ExecutionContext.Run is an extremely hot function and used by every await, ThreadPool execution, etc. if (executionContext == null) - throw new InvalidOperationException(SR.InvalidOperation_NullContext); + { + ThrowNullContext(); + } - Thread currentThread = Thread.CurrentThread; - ExecutionContextSwitcher ecsw = default(ExecutionContextSwitcher); + RunInternal(executionContext, callback, state); + } + + internal static void RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) + { + // Note: ExecutionContext.RunInternal is an extremely hot function and used by every await, ThreadPool execution, etc. + // Note: Manual enregistering may be addressed by "Exception Handling Write Through Optimization" + // https://github.com/dotnet/coreclr/blob/master/Documentation/design-docs/eh-writethru.md + + // Enregister variables with 0 post-fix so they can be used in registers without EH forcing them to stack + // Capture references to Thread Contexts + Thread currentThread0 = Thread.CurrentThread; + Thread currentThread = currentThread0; + ExecutionContext previousExecutionCtx0 = currentThread0.ExecutionContext; + + // Store current ExecutionContext and SynchronizationContext as "previousXxx". + // This allows us to restore them and undo any Context changes made in callback.Invoke + // so that they won't "leak" back into caller. + // These variables will cross EH so be forced to stack + ExecutionContext previousExecutionCtx = previousExecutionCtx0; + SynchronizationContext previousSyncCtx = currentThread0.SynchronizationContext; + + if (executionContext != null && executionContext.m_isDefault) + { + // Default is a null ExecutionContext internally + executionContext = null; + } + + if (previousExecutionCtx0 != executionContext) + { + // Restore changed ExecutionContext + currentThread0.ExecutionContext = executionContext; + if ((executionContext != null && executionContext.HasChangeNotifications) || + (previousExecutionCtx0 != null && previousExecutionCtx0.HasChangeNotifications)) + { + // There are change notifications; trigger any affected + OnValuesChanged(previousExecutionCtx0, executionContext); + } + } + + ExceptionDispatchInfo edi = null; try { - EstablishCopyOnWriteScope(currentThread, ref ecsw); - ExecutionContext.Restore(currentThread, executionContext); - callback(state); + callback.Invoke(state); } - catch + catch (Exception ex) { // Note: we have a "catch" rather than a "finally" because we want // to stop the first pass of EH here. That way we can restore the previous - // context before any of our callers' EH filters run. That means we need to - // end the scope separately in the non-exceptional case below. - ecsw.Undo(currentThread); - throw; + // context before any of our callers' EH filters run. + edi = ExceptionDispatchInfo.Capture(ex); } - ecsw.Undo(currentThread); - } - internal static void Restore(Thread currentThread, ExecutionContext executionContext) - { - Debug.Assert(currentThread == Thread.CurrentThread); - - ExecutionContext previous = currentThread.ExecutionContext ?? Default; - currentThread.ExecutionContext = executionContext; - - // New EC could be null if that's what ECS.Undo saved off. - // For the purposes of dealing with context change, treat this as the default EC - executionContext = executionContext ?? Default; - - if (previous != executionContext) + // Re-enregistrer variables post EH with 1 post-fix so they can be used in registers rather than from stack + SynchronizationContext previousSyncCtx1 = previousSyncCtx; + Thread currentThread1 = currentThread; + // The common case is that these have not changed, so avoid the cost of a write barrier if not needed. + if (currentThread1.SynchronizationContext != previousSyncCtx1) { - OnContextChanged(previous, executionContext); + // Restore changed SynchronizationContext back to previous + currentThread1.SynchronizationContext = previousSyncCtx1; } - } - internal static void EstablishCopyOnWriteScope(Thread currentThread, ref ExecutionContextSwitcher ecsw) - { - Debug.Assert(currentThread == Thread.CurrentThread); + ExecutionContext previousExecutionCtx1 = previousExecutionCtx; + ExecutionContext currentExecutionCtx1 = currentThread1.ExecutionContext; + if (currentExecutionCtx1 != previousExecutionCtx1) + { + // Restore changed ExecutionContext back to previous + currentThread1.ExecutionContext = previousExecutionCtx1; + if ((currentExecutionCtx1 != null && currentExecutionCtx1.HasChangeNotifications) || + (previousExecutionCtx1 != null && previousExecutionCtx1.HasChangeNotifications)) + { + // There are change notifications; trigger any affected + OnValuesChanged(currentExecutionCtx1, previousExecutionCtx1); + } + } - ecsw.m_ec = currentThread.ExecutionContext; - ecsw.m_sc = currentThread.SynchronizationContext; + // If exception was thrown by callback, rethrow it now original contexts are restored + edi?.Throw(); } - private static void OnContextChanged(ExecutionContext previous, ExecutionContext current) + internal static void OnValuesChanged(ExecutionContext previousExecutionCtx, ExecutionContext nextExecutionCtx) { - Debug.Assert(previous != null); - Debug.Assert(current != null); - Debug.Assert(previous != current); + Debug.Assert(previousExecutionCtx != nextExecutionCtx); - foreach (IAsyncLocal local in previous.m_localChangeNotifications) - { - object previousValue; - object currentValue; - previous.m_localValues.TryGetValue(local, out previousValue); - current.m_localValues.TryGetValue(local, out currentValue); + // Collect Change Notifications + IAsyncLocal[] previousChangeNotifications = previousExecutionCtx?.m_localChangeNotifications; + IAsyncLocal[] nextChangeNotifications = nextExecutionCtx?.m_localChangeNotifications; - if (previousValue != currentValue) - local.OnValueChanged(previousValue, currentValue, true); - } + // At least one side must have notifications + Debug.Assert(previousChangeNotifications != null || nextChangeNotifications != null); - if (current.m_localChangeNotifications != previous.m_localChangeNotifications) + // Fire Change Notifications + try { - try + if (previousChangeNotifications != null && nextChangeNotifications != null) { - foreach (IAsyncLocal local in current.m_localChangeNotifications) + // Notifications can't exist without values + Debug.Assert(previousExecutionCtx.m_localValues != null); + Debug.Assert(nextExecutionCtx.m_localValues != null); + // Both contexts have change notifications, check previousExecutionCtx first + foreach (IAsyncLocal local in previousChangeNotifications) { - // If the local has a value in the previous context, we already fired the event for that local - // in the code above. - object previousValue; - if (!previous.m_localValues.TryGetValue(local, out previousValue)) + previousExecutionCtx.m_localValues.TryGetValue(local, out object previousValue); + nextExecutionCtx.m_localValues.TryGetValue(local, out object currentValue); + + if (previousValue != currentValue) { - object currentValue; - current.m_localValues.TryGetValue(local, out currentValue); + local.OnValueChanged(previousValue, currentValue, contextChanged: true); + } + } - if (previousValue != currentValue) - local.OnValueChanged(previousValue, currentValue, true); + if (nextChangeNotifications != previousChangeNotifications) + { + // Check for additional notifications in nextExecutionCtx + foreach (IAsyncLocal local in nextChangeNotifications) + { + // If the local has a value in the previous context, we already fired the event + // for that local in the code above. + if (!previousExecutionCtx.m_localValues.TryGetValue(local, out object previousValue)) + { + nextExecutionCtx.m_localValues.TryGetValue(local, out object currentValue); + if (previousValue != currentValue) + { + local.OnValueChanged(previousValue, currentValue, contextChanged: true); + } + } } } } - catch (Exception ex) + else if (previousChangeNotifications != null) { - Environment.FailFast( - SR.ExecutionContext_ExceptionInAsyncLocalNotification, - ex); + // Notifications can't exist without values + Debug.Assert(previousExecutionCtx.m_localValues != null); + // No current values, so just check previous against null + foreach (IAsyncLocal local in previousChangeNotifications) + { + previousExecutionCtx.m_localValues.TryGetValue(local, out object previousValue); + if (previousValue != null) + { + local.OnValueChanged(previousValue, null, contextChanged: true); + } + } } + else // Implied: nextChangeNotifications != null + { + // Notifications can't exist without values + Debug.Assert(nextExecutionCtx.m_localValues != null); + // No previous values, so just check current against null + foreach (IAsyncLocal local in nextChangeNotifications) + { + nextExecutionCtx.m_localValues.TryGetValue(local, out object currentValue); + if (currentValue != null) + { + local.OnValueChanged(null, currentValue, contextChanged: true); + } + } + } + } + catch (Exception ex) + { + Environment.FailFast( + SR.ExecutionContext_ExceptionInAsyncLocalNotification, + ex); } } + [StackTraceHidden] + private static void ThrowNullContext() + { + throw new InvalidOperationException(SR.InvalidOperation_NullContext); + } + internal static object GetLocalValue(IAsyncLocal local) { ExecutionContext current = Thread.CurrentThread.ExecutionContext; if (current == null) + { return null; + } - object value; - current.m_localValues.TryGetValue(local, out value); + current.m_localValues.TryGetValue(local, out object value); return value; } internal static void SetLocalValue(IAsyncLocal local, object newValue, bool needChangeNotifications) { - ExecutionContext current = Thread.CurrentThread.ExecutionContext ?? ExecutionContext.Default; + ExecutionContext current = Thread.CurrentThread.ExecutionContext; - object previousValue; - bool hadPreviousValue = current.m_localValues.TryGetValue(local, out previousValue); + object previousValue = null; + bool hadPreviousValue = false; + if (current != null) + { + hadPreviousValue = current.m_localValues.TryGetValue(local, out previousValue); + } if (previousValue == newValue) + { return; + } - IAsyncLocalValueMap newValues = current.m_localValues.Set(local, newValue); + IAsyncLocal[] newChangeNotifications = null; + IAsyncLocalValueMap newValues; + bool isFlowSuppressed = false; + if (current != null) + { + isFlowSuppressed = current.m_isFlowSuppressed; + newValues = current.m_localValues.Set(local, newValue); + newChangeNotifications = current.m_localChangeNotifications; + } + else + { + // First AsyncLocal + newValues = new AsyncLocalValueMap.OneElementAsyncLocalValueMap(local, newValue); + } // // Either copy the change notification array, or create a new one, depending on whether we need to add a new item. // - IAsyncLocal[] newChangeNotifications = current.m_localChangeNotifications; if (needChangeNotifications) { if (hadPreviousValue) { + Debug.Assert(newChangeNotifications != null); Debug.Assert(Array.IndexOf(newChangeNotifications, local) >= 0); } + else if (newChangeNotifications == null) + { + newChangeNotifications = new IAsyncLocal[1] { local }; + } else { int newNotificationIndex = newChangeNotifications.Length; @@ -264,12 +358,14 @@ namespace System.Threading } } - Thread.CurrentThread.ExecutionContext = - new ExecutionContext(newValues, newChangeNotifications, current.m_isFlowSuppressed); + Thread.CurrentThread.ExecutionContext = + (!isFlowSuppressed && newValues.GetType() == typeof(AsyncLocalValueMap.EmptyAsyncLocalValueMap)) ? + null : // No values, return to Default context + new ExecutionContext(newValues, newChangeNotifications, isFlowSuppressed); if (needChangeNotifications) { - local.OnValueChanged(previousValue, newValue, false); + local.OnValueChanged(previousValue, newValue, contextChanged: false); } } diff --git a/src/mscorlib/src/System/IO/Stream.cs b/src/mscorlib/src/System/IO/Stream.cs index d9ed08f..63cc0c9 100644 --- a/src/mscorlib/src/System/IO/Stream.cs +++ b/src/mscorlib/src/System/IO/Stream.cs @@ -673,7 +673,7 @@ namespace System.IO var invokeAsyncCallback = s_invokeAsyncCallback; if (invokeAsyncCallback == null) s_invokeAsyncCallback = invokeAsyncCallback = InvokeAsyncCallback; // benign race condition - ExecutionContext.Run(context, invokeAsyncCallback, this); + ExecutionContext.RunInternal(context, invokeAsyncCallback, this); } } diff --git a/src/mscorlib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs b/src/mscorlib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs index 022f15a..de38583 100644 --- a/src/mscorlib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs +++ b/src/mscorlib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs @@ -49,8 +49,9 @@ namespace System.Runtime.CompilerServices /// The state machine instance, passed by reference. /// The argument was null (Nothing in Visual Basic). [DebuggerStepThrough] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine => - _builder.Start(ref stateMachine); + AsyncMethodBuilder.Start(ref stateMachine); /// Associates the builder with the state machine it represents. /// The heap-allocated state machine object. @@ -198,8 +199,9 @@ namespace System.Runtime.CompilerServices /// Specifies the type of the state machine. /// The state machine instance, passed by reference. [DebuggerStepThrough] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine => - m_builder.Start(ref stateMachine); + AsyncMethodBuilder.Start(ref stateMachine); /// Associates the builder with the state machine it represents. /// The heap-allocated state machine object. @@ -312,29 +314,9 @@ namespace System.Runtime.CompilerServices /// Specifies the type of the state machine. /// The state machine instance, passed by reference. [DebuggerStepThrough] - public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine - { - if (stateMachine == null) // TStateMachines are generally non-nullable value types, so this check will be elided - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine); - } - - // Run the MoveNext method within a copy-on-write ExecutionContext scope. - // This allows us to undo any ExecutionContext changes made in MoveNext, - // so that they won't "leak" out of the first await. - - Thread currentThread = Thread.CurrentThread; - ExecutionContextSwitcher ecs = default(ExecutionContextSwitcher); - try - { - ExecutionContext.EstablishCopyOnWriteScope(currentThread, ref ecs); - stateMachine.MoveNext(); - } - finally - { - ecs.Undo(currentThread); - } - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine => + AsyncMethodBuilder.Start(ref stateMachine); /// Associates the builder with the state machine it represents. /// The heap-allocated state machine object. @@ -548,13 +530,14 @@ namespace System.Runtime.CompilerServices /// Calls MoveNext on public void MoveNext() { - if (Context == null) + ExecutionContext context = Context; + if (context == null) { StateMachine.MoveNext(); } else { - ExecutionContext.Run(Context, s_callback, this); + ExecutionContext.RunInternal(context, s_callback, this); } // In case this is a state machine box with a finalizer, suppress its finalization diff --git a/src/mscorlib/src/System/Threading/CancellationTokenSource.cs b/src/mscorlib/src/System/Threading/CancellationTokenSource.cs index 9ace8d3..07b9328 100644 --- a/src/mscorlib/src/System/Threading/CancellationTokenSource.cs +++ b/src/mscorlib/src/System/Threading/CancellationTokenSource.cs @@ -924,9 +924,10 @@ namespace System.Threading public void ExecuteCallback() { - if (ExecutionContext != null) + ExecutionContext context = ExecutionContext; + if (context != null) { - ExecutionContext.Run(ExecutionContext, s => + ExecutionContext.RunInternal(context, s => { CallbackNode n = (CallbackNode)s; n.Callback(n.CallbackState); diff --git a/src/mscorlib/src/System/Threading/Overlapped.cs b/src/mscorlib/src/System/Threading/Overlapped.cs index 44fe29c..660e9f4 100644 --- a/src/mscorlib/src/System/Threading/Overlapped.cs +++ b/src/mscorlib/src/System/Threading/Overlapped.cs @@ -94,7 +94,7 @@ namespace System.Threading overlapped = OverlappedData.GetOverlappedFromNative(pOVERLAP).m_overlapped; helper = overlapped.iocbHelper; - if (helper == null || helper._executionContext == null || helper._executionContext == ExecutionContext.Default) + if (helper == null || helper._executionContext == null || helper._executionContext.IsDefault) { // We got here because of UnsafePack (or) Pack with EC flow supressed IOCompletionCallback callback = overlapped.UserCallback; @@ -106,7 +106,7 @@ namespace System.Threading helper._errorCode = errorCode; helper._numBytes = numBytes; helper._pOVERLAP = pOVERLAP; - ExecutionContext.Run(helper._executionContext, _ccb, helper); + ExecutionContext.RunInternal(helper._executionContext, _ccb, helper); } //Quickly check the VM again, to see if a packet has arrived. diff --git a/src/mscorlib/src/System/Threading/Tasks/Task.cs b/src/mscorlib/src/System/Threading/Tasks/Task.cs index 5c9754f..56ae63f 100644 --- a/src/mscorlib/src/System/Threading/Tasks/Task.cs +++ b/src/mscorlib/src/System/Threading/Tasks/Task.cs @@ -2437,7 +2437,7 @@ namespace System.Threading.Tasks else { // Invoke it under the captured ExecutionContext - ExecutionContext.Run(ec, s_ecCallback, this); + ExecutionContext.RunInternal(ec, s_ecCallback, this); } } catch (Exception exn) diff --git a/src/mscorlib/src/System/Threading/Tasks/TaskContinuation.cs b/src/mscorlib/src/System/Threading/Tasks/TaskContinuation.cs index 42fc40e..cb18c66 100644 --- a/src/mscorlib/src/System/Threading/Tasks/TaskContinuation.cs +++ b/src/mscorlib/src/System/Threading/Tasks/TaskContinuation.cs @@ -640,14 +640,15 @@ namespace System.Threading.Tasks // We're on a thread pool thread with no higher-level callers, so exceptions can just propagate. // If there's no execution context, just invoke the delegate. - if (m_capturedContext == null) + ExecutionContext context = m_capturedContext; + if (context == null) { m_action(); } // If there is an execution context, get the cached delegate and run the action under the context. else { - ExecutionContext.Run(m_capturedContext, GetInvokeActionCallback(), m_action); + ExecutionContext.RunInternal(context, GetInvokeActionCallback(), m_action); } } finally @@ -708,10 +709,17 @@ namespace System.Threading.Tasks { if (prevCurrentTask != null) currentTask = null; - // If there's no captured context, just run the callback directly. - if (m_capturedContext == null) callback(state); - // Otherwise, use the captured context to do so. - else ExecutionContext.Run(m_capturedContext, callback, state); + ExecutionContext context = m_capturedContext; + if (context == null) + { + // If there's no captured context, just run the callback directly. + callback(state); + } + else + { + // Otherwise, use the captured context to do so. + ExecutionContext.RunInternal(context, callback, state); + } } catch (Exception exc) // we explicitly do not request handling of dangerous exceptions like AVs { diff --git a/src/mscorlib/src/System/Threading/Thread.cs b/src/mscorlib/src/System/Threading/Thread.cs index c9ae3d8..aea3616 100644 --- a/src/mscorlib/src/System/Threading/Thread.cs +++ b/src/mscorlib/src/System/Threading/Thread.cs @@ -65,9 +65,10 @@ namespace System.Threading internal void ThreadStart(object obj) { _startArg = obj; - if (_executionContext != null) + ExecutionContext context = _executionContext; + if (context != null) { - ExecutionContext.Run(_executionContext, _ccb, (Object)this); + ExecutionContext.RunInternal(context, _ccb, (Object)this); } else { @@ -78,9 +79,10 @@ namespace System.Threading // call back helper internal void ThreadStart() { - if (_executionContext != null) + ExecutionContext context = _executionContext; + if (context != null) { - ExecutionContext.Run(_executionContext, _ccb, (Object)this); + ExecutionContext.RunInternal(context, _ccb, (Object)this); } else { diff --git a/src/mscorlib/src/System/Threading/ThreadPool.cs b/src/mscorlib/src/System/Threading/ThreadPool.cs index 1b59163..281e5ab 100644 --- a/src/mscorlib/src/System/Threading/ThreadPool.cs +++ b/src/mscorlib/src/System/Threading/ThreadPool.cs @@ -896,9 +896,9 @@ namespace System.Threading internal sealed class QueueUserWorkItemCallback : IThreadPoolWorkItem { - private WaitCallback callback; - private readonly ExecutionContext context; - private readonly Object state; + private WaitCallback _callback; + private readonly ExecutionContext _context; + private readonly Object _state; #if DEBUG private volatile int executed; @@ -921,9 +921,9 @@ namespace System.Threading internal QueueUserWorkItemCallback(WaitCallback waitCallback, Object stateObj, ExecutionContext ec) { - callback = waitCallback; - state = stateObj; - context = ec; + _callback = waitCallback; + _state = stateObj; + _context = ec; } void IThreadPoolWorkItem.ExecuteWorkItem() @@ -932,15 +932,16 @@ namespace System.Threading MarkExecuted(aborted: false); #endif // call directly if it is an unsafe call OR EC flow is suppressed + ExecutionContext context = _context; if (context == null) { - WaitCallback cb = callback; - callback = null; - cb(state); + WaitCallback cb = _callback; + _callback = null; + cb(_state); } else { - ExecutionContext.Run(context, ccb, this); + ExecutionContext.RunInternal(context, ccb, this); } } @@ -958,9 +959,9 @@ namespace System.Threading private static void WaitCallback_Context(Object state) { QueueUserWorkItemCallback obj = (QueueUserWorkItemCallback)state; - WaitCallback wc = obj.callback; + WaitCallback wc = obj._callback; Debug.Assert(null != wc); - wc(obj.state); + wc(obj._state); } } @@ -999,7 +1000,8 @@ namespace System.Threading #if DEBUG MarkExecuted(aborted: false); #endif - ExecutionContext.Run(ExecutionContext.Default, ccb, this); + // null executionContext on RunInternal is Default context + ExecutionContext.RunInternal(executionContext: null, ccb, this); } void IThreadPoolWorkItem.MarkAborted(ThreadAbortException tae) @@ -1061,14 +1063,15 @@ namespace System.Threading _ThreadPoolWaitOrTimerCallback helper = (_ThreadPoolWaitOrTimerCallback)state; Debug.Assert(helper != null, "Null state passed to PerformWaitOrTimerCallback!"); // call directly if it is an unsafe call OR EC flow is suppressed - if (helper._executionContext == null) + ExecutionContext context = helper._executionContext; + if (context == null) { WaitOrTimerCallback callback = helper._waitOrTimerCallback; callback(helper._state, timedOut); } else { - ExecutionContext.Run(helper._executionContext, timedOut ? _ccbt : _ccbf, helper); + ExecutionContext.Run(context, timedOut ? _ccbt : _ccbf, helper); } } } @@ -1284,7 +1287,7 @@ namespace System.Threading ExecutionContext context = ExecutionContext.Capture(); - IThreadPoolWorkItem tpcallBack = context == ExecutionContext.Default ? + IThreadPoolWorkItem tpcallBack = (context != null && context.IsDefault) ? new QueueUserWorkItemCallbackDefaultContext(callBack, state) : (IThreadPoolWorkItem)new QueueUserWorkItemCallback(callBack, state, context); diff --git a/src/mscorlib/src/System/Threading/Timer.cs b/src/mscorlib/src/System/Threading/Timer.cs index 991f53f..90dd207 100644 --- a/src/mscorlib/src/System/Threading/Timer.cs +++ b/src/mscorlib/src/System/Threading/Timer.cs @@ -600,13 +600,14 @@ namespace System.Threading FrameworkEventSource.Log.ThreadTransferReceiveObj(this, 1, string.Empty); // call directly if EC flow is suppressed - if (m_executionContext == null) + ExecutionContext context = m_executionContext; + if (context == null) { m_timerCallback(m_state); } else { - ExecutionContext.Run(m_executionContext, s_callCallbackInContext, this); + ExecutionContext.RunInternal(context, s_callCallbackInContext, this); } }