public void Undo() { if (_thread == null) { throw new InvalidOperationException(SR.InvalidOperation_CannotUseAFCMultiple); } if (Thread.CurrentThread != _thread) { throw new InvalidOperationException(SR.InvalidOperation_CannotUseAFCOtherThread); } // An async flow control cannot be undone when a different execution context is applied. The desktop framework // mutates the execution context when its state changes, and only changes the instance when an execution context // is applied (for instance, through ExecutionContext.Run). The framework prevents a suppressed-flow execution // context from being applied by returning null from ExecutionContext.Capture, so the only type of execution // context that can be applied is one whose flow is not suppressed. After suppressing flow and changing an async // local's value, the desktop framework verifies that a different execution context has not been applied by // checking the execution context instance against the one saved from when flow was suppressed. In .NET Core, // since the execution context instance will change after changing the async local's value, it verifies that a // different execution context has not been applied, by instead ensuring that the current execution context's // flow is suppressed. if (!ExecutionContext.IsFlowSuppressed()) { throw new InvalidOperationException(SR.InvalidOperation_AsyncFlowCtrlCtxMismatch); } Contract.EndContractBlock(); _thread = null; ExecutionContext.RestoreFlow(); }
public static void Run(ExecutionContext executionContext, ContextCallback callback, Object state) { if (executionContext == null) { throw new InvalidOperationException(SR.InvalidOperation_NullContext); } Thread currentThread = Thread.CurrentThread; ExecutionContextSwitcher ecsw = default(ExecutionContextSwitcher); try { EstablishCopyOnWriteScope(currentThread, ref ecsw); ExecutionContext.Restore(currentThread, executionContext); callback(state); } catch { // 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; } ecsw.Undo(currentThread); }
internal static void EstablishCopyOnWriteScope(Thread currentThread, ref ExecutionContextSwitcher ecsw) { Debug.Assert(currentThread == Thread.CurrentThread); ecsw.m_ec = currentThread.ExecutionContext; ecsw.m_sc = currentThread.SynchronizationContext; }
internal static void RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, object state) { Debug.Assert(threadPoolThread == Thread.CurrentThread); CheckThreadPoolAndContextsAreDefault(); // ThreadPool starts on Default Context so we don't need to save the "previous" state as we know it is Default (null) if (executionContext != null && executionContext.m_isDefault) { // Default is a null ExecutionContext internally executionContext = null; } else if (executionContext != null) { // Non-Default context to restore threadPoolThread.ExecutionContext = executionContext; if (executionContext.HasChangeNotifications) { // There are change notifications; trigger any affected OnValuesChanged(previousExecutionCtx: null, executionContext); } } ExceptionDispatchInfo edi = null; try { callback.Invoke(state); } 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. edi = ExceptionDispatchInfo.Capture(ex); } // Enregister threadPoolThread as it crossed EH, and use enregistered variable Thread currentThread = threadPoolThread; ExecutionContext currentExecutionCtx = currentThread.ExecutionContext; // Restore changed SynchronizationContext back to Default currentThread.SynchronizationContext = null; if (currentExecutionCtx != null) { // The EC always needs to be reset for this overload, as it will flow back to the caller if it performs // extra work prior to returning to the Dispatch loop. For example for Task-likes it will flow out of await points // Restore to Default before Notifications, as the change can be observed in the handler. currentThread.ExecutionContext = null; if (currentExecutionCtx.HasChangeNotifications) { // There are change notifications; trigger any affected OnValuesChanged(currentExecutionCtx, nextExecutionCtx: null); } } // If exception was thrown by callback, rethrow it now original contexts are restored edi?.Throw(); }
public static void RestoreFlow() { Thread currentThread = Thread.CurrentThread; ExecutionContext executionContext = currentThread.ExecutionContext; if (executionContext == null || !executionContext.m_isFlowSuppressed) { throw new InvalidOperationException(SR.InvalidOperation_CannotRestoreUnsupressedFlow); } currentThread.ExecutionContext = executionContext.ShallowClone(isFlowSuppressed: false); }
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); } }
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) { OnContextChanged(previous, executionContext); } }
public static AsyncFlowControl SuppressFlow() { Thread currentThread = Thread.CurrentThread; ExecutionContext executionContext = currentThread.ExecutionContext ?? Default; if (executionContext.m_isFlowSuppressed) { throw new InvalidOperationException(SR.InvalidOperation_CannotSupressFlowMultipleTimes); } executionContext = executionContext.ShallowClone(isFlowSuppressed: true); var asyncFlowControl = new AsyncFlowControl(); currentThread.ExecutionContext = executionContext; asyncFlowControl.Initialize(currentThread); return(asyncFlowControl); }
internal void Initialize(Thread currentThread) { Debug.Assert(currentThread == Thread.CurrentThread); _thread = currentThread; }
// Direct copy of the above RunInternal overload, except that it passes the state into the callback strongly-typed and by ref. internal static void RunInternal <TState>(ExecutionContext executionContext, ContextCallback <TState> callback, ref TState 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; if (previousExecutionCtx0 != null && previousExecutionCtx0.m_isDefault) { // Default is a null ExecutionContext internally previousExecutionCtx0 = null; } // 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) { RestoreChangedContextToThread(currentThread0, executionContext, previousExecutionCtx0); } ExceptionDispatchInfo edi = null; try { callback.Invoke(ref state); } 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. edi = ExceptionDispatchInfo.Capture(ex); } // 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) { // Restore changed SynchronizationContext back to previous currentThread1.SynchronizationContext = previousSyncCtx1; } ExecutionContext previousExecutionCtx1 = previousExecutionCtx; ExecutionContext currentExecutionCtx1 = currentThread1.ExecutionContext; if (currentExecutionCtx1 != previousExecutionCtx1) { RestoreChangedContextToThread(currentThread1, previousExecutionCtx1, currentExecutionCtx1); } // If exception was thrown by callback, rethrow it now original contexts are restored edi?.Throw(); }