public void AwaitOnCompleted <TAwaiter, TStateMachine>( ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : INotifyCompletion where TStateMachine : IAsyncStateMachine { EnsureHasTask(); StateMachineBox <TStateMachine> .AwaitOnCompleted(ref awaiter, ref stateMachine); }
/// <summary>Gets the "boxed" state machine object.</summary> /// <typeparam name="TStateMachine">Specifies the type of the async state machine.</typeparam> /// <param name="stateMachine">The state machine.</param> /// <param name="boxFieldRef">A reference to the field containing the initialized state machine box.</param> /// <returns>The "boxed" state machine.</returns> private static IAsyncStateMachineBox GetStateMachineBox <TStateMachine>( ref TStateMachine stateMachine, [NotNull] ref StateMachineBox?boxFieldRef) where TStateMachine : IAsyncStateMachine { ExecutionContext?currentContext = ExecutionContext.Capture(); // Check first for the most common case: not the first yield in an async method. // In this case, the first yield will have already "boxed" the state machine in // a strongly-typed manner into an AsyncStateMachineBox. It will already contain // the state machine as well as a MoveNextDelegate and a context. The only thing // we might need to do is update the context if that's changed since it was stored. if (boxFieldRef is StateMachineBox <TStateMachine> stronglyTypedBox) { if (stronglyTypedBox.Context != currentContext) { stronglyTypedBox.Context = currentContext; } return(stronglyTypedBox); } // The least common case: we have a weakly-typed boxed. This results if the debugger // or some other use of reflection accesses a property like ObjectIdForDebugger. In // such situations, we need to get an object to represent the builder, but we don't yet // know the type of the state machine, and thus can't use TStateMachine. Instead, we // use the IAsyncStateMachine interface, which all TStateMachines implement. This will // result in a boxing allocation when storing the TStateMachine if it's a struct, but // this only happens in active debugging scenarios where such performance impact doesn't // matter. if (boxFieldRef is StateMachineBox <IAsyncStateMachine> weaklyTypedBox) { // If this is the first await, we won't yet have a state machine, so store it. if (weaklyTypedBox.StateMachine is null) { Debugger.NotifyOfCrossThreadDependency(); // same explanation as with usage below weaklyTypedBox.StateMachine = stateMachine; } // Update the context. This only happens with a debugger, so no need to spend // extra IL checking for equality before doing the assignment. weaklyTypedBox.Context = currentContext; return(weaklyTypedBox); } // Alert a listening debugger that we can't make forward progress unless it slips threads. // If we don't do this, and a method that uses "await foo;" is invoked through funceval, // we could end up hooking up a callback to push forward the async method's state machine, // the debugger would then abort the funceval after it takes too long, and then continuing // execution could result in another callback being hooked up. At that point we have // multiple callbacks registered to push the state machine, which could result in bad behavior. Debugger.NotifyOfCrossThreadDependency(); // At this point, m_task should really be null, in which case we want to create the box. // However, in a variety of debugger-related (erroneous) situations, it might be non-null, // e.g. if the Task property is examined in a Watch window, forcing it to be lazily-intialized // as a Task<TResult> rather than as an ValueTaskStateMachineBox. The worst that happens in such // cases is we lose the ability to properly step in the debugger, as the debugger uses that // object's identity to track this specific builder/state machine. As such, we proceed to // overwrite whatever's there anyway, even if it's non-null. StateMachineBox <TStateMachine> box = StateMachineBox <TStateMachine> .RentFromCache(); boxFieldRef = box; // important: this must be done before storing stateMachine into box.StateMachine! box.StateMachine = stateMachine; box.Context = currentContext; return(box); }
public void AwaitUnsafeOnCompleted <TAwaiter, TStateMachine>( ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine => StateMachineBox <TStateMachine> .AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);