// 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. // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ // // // // Compiler-targeted types that build tasks for use as the return types of asynchronous methods. // // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- using System.Diagnostics; using System.Diagnostics.Tracing; using System.Reflection; using System.Runtime.ExceptionServices; #if FEATURE_COMINTEROP using System.Runtime.InteropServices.WindowsRuntime; #endif // FEATURE_COMINTEROP using System.Threading; using System.Threading.Tasks; using System.Text; using Internal.Runtime.CompilerServices; using Internal.Runtime.Augments; #if CORERT using Thread = Internal.Runtime.Augments.RuntimeThread; #endif namespace System.Runtime.CompilerServices { /// /// Provides a builder for asynchronous methods that return void. /// This type is intended for compiler use only. /// public struct AsyncVoidMethodBuilder { /// The synchronization context associated with this operation. private SynchronizationContext _synchronizationContext; /// The builder this void builder wraps. private AsyncTaskMethodBuilder _builder; // mutable struct: must not be readonly /// Initializes a new . /// The initialized . public static AsyncVoidMethodBuilder Create() { SynchronizationContext sc = SynchronizationContext.Current; sc?.OperationStarted(); return new AsyncVoidMethodBuilder() { _synchronizationContext = sc }; } /// Initiates the builder's execution with the associated state machine. /// Specifies the type of the state machine. /// 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 => AsyncMethodBuilderCore.Start(ref stateMachine); /// Associates the builder with the state machine it represents. /// The heap-allocated state machine object. /// The argument was null (Nothing in Visual Basic). /// The builder is incorrectly initialized. public void SetStateMachine(IAsyncStateMachine stateMachine) => _builder.SetStateMachine(stateMachine); /// /// Schedules the specified state machine to be pushed forward when the specified awaiter completes. /// /// Specifies the type of the awaiter. /// Specifies the type of the state machine. /// The awaiter. /// The state machine. public void AwaitOnCompleted( ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : INotifyCompletion where TStateMachine : IAsyncStateMachine => _builder.AwaitOnCompleted(ref awaiter, ref stateMachine); /// /// Schedules the specified state machine to be pushed forward when the specified awaiter completes. /// /// Specifies the type of the awaiter. /// Specifies the type of the state machine. /// The awaiter. /// The state machine. public void AwaitUnsafeOnCompleted( ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine => _builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); /// Completes the method builder successfully. public void SetResult() { if (AsyncCausalityTracer.LoggingOn) { AsyncCausalityTracer.TraceOperationCompletion(this.Task, AsyncCausalityStatus.Completed); } // Mark the builder as completed. As this is a void-returning method, this mostly // doesn't matter, but it can affect things like debug events related to finalization. _builder.SetResult(); if (_synchronizationContext != null) { NotifySynchronizationContextOfCompletion(); } } /// Faults the method builder with an exception. /// The exception that is the cause of this fault. /// The argument is null (Nothing in Visual Basic). /// The builder is not initialized. public void SetException(Exception exception) { if (exception == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exception); } if (AsyncCausalityTracer.LoggingOn) { AsyncCausalityTracer.TraceOperationCompletion(this.Task, AsyncCausalityStatus.Error); } if (_synchronizationContext != null) { // If we captured a synchronization context, Post the throwing of the exception to it // and decrement its outstanding operation count. try { AsyncMethodBuilderCore.ThrowAsync(exception, targetContext: _synchronizationContext); } finally { NotifySynchronizationContextOfCompletion(); } } else { // Otherwise, queue the exception to be thrown on the ThreadPool. This will // result in a crash unless legacy exception behavior is enabled by a config // file or a CLR host. AsyncMethodBuilderCore.ThrowAsync(exception, targetContext: null); } // The exception was propagated already; we don't need or want to fault the builder, just mark it as completed. _builder.SetResult(); } /// Notifies the current synchronization context that the operation completed. private void NotifySynchronizationContextOfCompletion() { Debug.Assert(_synchronizationContext != null, "Must only be used with a non-null context."); try { _synchronizationContext.OperationCompleted(); } catch (Exception exc) { // If the interaction with the SynchronizationContext goes awry, // fall back to propagating on the ThreadPool. AsyncMethodBuilderCore.ThrowAsync(exc, targetContext: null); } } /// Lazily instantiate the Task in a non-thread-safe manner. private Task Task => _builder.Task; /// /// Gets an object that may be used to uniquely identify this builder to the debugger. /// /// /// This property lazily instantiates the ID in a non-thread-safe manner. /// It must only be used by the debugger and AsyncCausalityTracer in a single-threaded manner. /// internal object ObjectIdForDebugger => _builder.ObjectIdForDebugger; } /// /// Provides a builder for asynchronous methods that return . /// This type is intended for compiler use only. /// /// /// AsyncTaskMethodBuilder is a value type, and thus it is copied by value. /// Prior to being copied, one of its Task, SetResult, or SetException members must be accessed, /// or else the copies may end up building distinct Task instances. /// public struct AsyncTaskMethodBuilder { /// A cached VoidTaskResult task used for builders that complete synchronously. #if PROJECTN private static readonly Task s_cachedCompleted = AsyncTaskCache.CreateCacheableTask(default(VoidTaskResult)); #else private readonly static Task s_cachedCompleted = AsyncTaskMethodBuilder.s_defaultResultTask; #endif /// The generic builder object to which this non-generic instance delegates. private AsyncTaskMethodBuilder m_builder; // mutable struct: must not be readonly. Debugger depends on the exact name of this field. /// Initializes a new . /// The initialized . public static AsyncTaskMethodBuilder Create() => default; /// 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] [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine => AsyncMethodBuilderCore.Start(ref stateMachine); /// Associates the builder with the state machine it represents. /// The heap-allocated state machine object. /// The argument was null (Nothing in Visual Basic). /// The builder is incorrectly initialized. public void SetStateMachine(IAsyncStateMachine stateMachine) => m_builder.SetStateMachine(stateMachine); /// /// Schedules the specified state machine to be pushed forward when the specified awaiter completes. /// /// Specifies the type of the awaiter. /// Specifies the type of the state machine. /// The awaiter. /// The state machine. public void AwaitOnCompleted( ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : INotifyCompletion where TStateMachine : IAsyncStateMachine => m_builder.AwaitOnCompleted(ref awaiter, ref stateMachine); /// /// Schedules the specified state machine to be pushed forward when the specified awaiter completes. /// /// Specifies the type of the awaiter. /// Specifies the type of the state machine. /// The awaiter. /// The state machine. public void AwaitUnsafeOnCompleted( ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine => m_builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); /// Gets the for this builder. /// The representing the builder's asynchronous operation. /// The builder is not initialized. public Task Task { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => m_builder.Task; } /// /// Completes the in the /// RanToCompletion state. /// /// The builder is not initialized. /// The task has already completed. public void SetResult() => m_builder.SetResult(s_cachedCompleted); // Using s_cachedCompleted is faster than using s_defaultResultTask. /// /// Completes the in the /// Faulted state with the specified exception. /// /// The to use to fault the task. /// The argument is null (Nothing in Visual Basic). /// The builder is not initialized. /// The task has already completed. public void SetException(Exception exception) => m_builder.SetException(exception); /// /// Called by the debugger to request notification when the first wait operation /// (await, Wait, Result, etc.) on this builder's task completes. /// /// /// true to enable notification; false to disable a previously set notification. /// internal void SetNotificationForWaitCompletion(bool enabled) => m_builder.SetNotificationForWaitCompletion(enabled); /// /// Gets an object that may be used to uniquely identify this builder to the debugger. /// /// /// This property lazily instantiates the ID in a non-thread-safe manner. /// It must only be used by the debugger and tracing purposes, and only in a single-threaded manner /// when no other threads are in the middle of accessing this property or this.Task. /// internal object ObjectIdForDebugger => m_builder.ObjectIdForDebugger; } /// /// Provides a builder for asynchronous methods that return . /// This type is intended for compiler use only. /// /// /// AsyncTaskMethodBuilder{TResult} is a value type, and thus it is copied by value. /// Prior to being copied, one of its Task, SetResult, or SetException members must be accessed, /// or else the copies may end up building distinct Task instances. /// public struct AsyncTaskMethodBuilder { #if !PROJECTN /// A cached task for default(TResult). internal readonly static Task s_defaultResultTask = AsyncTaskCache.CreateCacheableTask(default(TResult)); #endif /// The lazily-initialized built task. private Task m_task; // lazily-initialized: must not be readonly. Debugger depends on the exact name of this field. /// Initializes a new . /// The initialized . public static AsyncTaskMethodBuilder Create() { return default; // NOTE: If this method is ever updated to perform more initialization, // other Create methods like AsyncTaskMethodBuilder.Create and // AsyncValueTaskMethodBuilder.Create must be updated to call this. } /// 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] [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine => AsyncMethodBuilderCore.Start(ref stateMachine); /// Associates the builder with the state machine it represents. /// The heap-allocated state machine object. /// The argument was null (Nothing in Visual Basic). /// The builder is incorrectly initialized. public void SetStateMachine(IAsyncStateMachine stateMachine) { if (stateMachine == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine); } if (m_task != null) { ThrowHelper.ThrowInvalidOperationException(ExceptionResource.AsyncMethodBuilder_InstanceNotInitialized); } // SetStateMachine was originally needed in order to store the boxed state machine reference into // the boxed copy. Now that a normal box is no longer used, SetStateMachine is also legacy. We need not // do anything here, and thus assert to ensure we're not calling this from our own implementations. Debug.Fail("SetStateMachine should not be used."); } /// /// Schedules the specified state machine to be pushed forward when the specified awaiter completes. /// /// Specifies the type of the awaiter. /// Specifies the type of the state machine. /// The awaiter. /// The state machine. public void AwaitOnCompleted( ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : INotifyCompletion where TStateMachine : IAsyncStateMachine { try { awaiter.OnCompleted(GetStateMachineBox(ref stateMachine).MoveNextAction); } catch (Exception e) { AsyncMethodBuilderCore.ThrowAsync(e, targetContext: null); } } /// /// Schedules the specified state machine to be pushed forward when the specified awaiter completes. /// /// Specifies the type of the awaiter. /// Specifies the type of the state machine. /// The awaiter. /// The state machine. public void AwaitUnsafeOnCompleted( ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine { IAsyncStateMachineBox box = GetStateMachineBox(ref stateMachine); // The null tests here ensure that the jit can optimize away the interface // tests when TAwaiter is a ref type. if ((null != (object)default(TAwaiter)) && (awaiter is ITaskAwaiter)) { ref TaskAwaiter ta = ref Unsafe.As(ref awaiter); // relies on TaskAwaiter/TaskAwaiter having the same layout TaskAwaiter.UnsafeOnCompletedInternal(ta.m_task, box, continueOnCapturedContext: true); } else if ((null != (object)default(TAwaiter)) && (awaiter is IConfiguredTaskAwaiter)) { ref ConfiguredTaskAwaitable.ConfiguredTaskAwaiter ta = ref Unsafe.As(ref awaiter); TaskAwaiter.UnsafeOnCompletedInternal(ta.m_task, box, ta.m_continueOnCapturedContext); } else if ((null != (object)default(TAwaiter)) && (awaiter is IStateMachineBoxAwareAwaiter)) { try { ((IStateMachineBoxAwareAwaiter)awaiter).AwaitUnsafeOnCompleted(box); } catch (Exception e) { // Whereas with Task the code that hooks up and invokes the continuation is all local to corelib, // with ValueTaskAwaiter we may be calling out to an arbitrary implementation of IValueTaskSource // wrapped in the ValueTask, and as such we protect against errant exceptions that may emerge. // We don't want such exceptions propagating back into the async method, which can't handle // exceptions well at that location in the state machine, especially if the exception may occur // after the ValueTaskAwaiter already successfully hooked up the callback, in which case it's possible // two different flows of execution could end up happening in the same async method call. AsyncMethodBuilderCore.ThrowAsync(e, targetContext: null); } } else { // The awaiter isn't specially known. Fall back to doing a normal await. try { awaiter.UnsafeOnCompleted(box.MoveNextAction); } catch (Exception e) { AsyncMethodBuilderCore.ThrowAsync(e, targetContext: null); } } } /// Gets the "boxed" state machine object. /// Specifies the type of the async state machine. /// The state machine. /// The "boxed" state machine. private IAsyncStateMachineBox GetStateMachineBox( ref TStateMachine stateMachine) 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 (m_task is AsyncStateMachineBox 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 or a // method like SetNotificationForWaitCompletion prior to the first await happening. 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 (m_task is AsyncStateMachineBox weaklyTypedBox) { // If this is the first await, we won't yet have a state machine, so store it. if (weaklyTypedBox.StateMachine == 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 rather than as an AsyncStateMachineBox. 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. #if CORERT // DebugFinalizableAsyncStateMachineBox looks like a small type, but it actually is not because // it will have a copy of all the slots from its parent. It will add another hundred(s) bytes // per each async method in CoreRT / ProjectN binaries without adding much value. Avoid // generating this extra code until a better solution is implemented. var box = new AsyncStateMachineBox(); #else var box = AsyncMethodBuilderCore.TrackAsyncMethodCompletion ? CreateDebugFinalizableAsyncStateMachineBox() : new AsyncStateMachineBox(); #endif m_task = box; // important: this must be done before storing stateMachine into box.StateMachine! box.StateMachine = stateMachine; box.Context = currentContext; return box; } #if !CORERT // Avoid forcing the JIT to build DebugFinalizableAsyncStateMachineBox unless it's actually needed. [MethodImpl(MethodImplOptions.NoInlining)] private static AsyncStateMachineBox CreateDebugFinalizableAsyncStateMachineBox() where TStateMachine : IAsyncStateMachine => new DebugFinalizableAsyncStateMachineBox(); /// /// Provides an async state machine box with a finalizer that will fire an EventSource /// event about the state machine if it's being finalized without having been completed. /// /// Specifies the type of the state machine. private sealed class DebugFinalizableAsyncStateMachineBox : // SOS DumpAsync command depends on this name AsyncStateMachineBox where TStateMachine : IAsyncStateMachine { ~DebugFinalizableAsyncStateMachineBox() { // If the state machine is being finalized, something went wrong during its processing, // e.g. it awaited something that got collected without itself having been completed. // Fire an event with details about the state machine to help with debugging. if (!IsCompleted) // double-check it's not completed, just to help minimize false positives { TplEtwProvider.Log.IncompleteAsyncMethod(this); } } } #endif /// A strongly-typed box for Task-based async state machines. /// Specifies the type of the state machine. private class AsyncStateMachineBox : // SOS DumpAsync command depends on this name Task, IAsyncStateMachineBox where TStateMachine : IAsyncStateMachine { /// Delegate used to invoke on an ExecutionContext when passed an instance of this box type. private static readonly ContextCallback s_callback = s => { Debug.Assert(s is AsyncStateMachineBox); // Only used privately to pass directly to EC.Run Unsafe.As>(s).StateMachine.MoveNext(); }; /// A delegate to the method. private Action _moveNextAction; /// The state machine itself. public TStateMachine StateMachine; // mutable struct; do not make this readonly. SOS DumpAsync command depends on this name. /// Captured ExecutionContext with which to invoke ; may be null. public ExecutionContext Context; /// A delegate to the method. public Action MoveNextAction => _moveNextAction ?? (_moveNextAction = new Action(MoveNext)); internal sealed override void ExecuteFromThreadPool(Thread threadPoolThread) => MoveNext(threadPoolThread); /// Calls MoveNext on public void MoveNext() => MoveNext(threadPoolThread: null); private void MoveNext(Thread threadPoolThread) { Debug.Assert(!IsCompleted); bool loggingOn = AsyncCausalityTracer.LoggingOn; if (loggingOn) { AsyncCausalityTracer.TraceSynchronousWorkStart(this, CausalitySynchronousWork.Execution); } ExecutionContext context = Context; if (context == null) { StateMachine.MoveNext(); } else { if (threadPoolThread is null) { ExecutionContext.RunInternal(context, s_callback, this); } else { ExecutionContext.RunFromThreadPoolDispatchLoop(threadPoolThread, context, s_callback, this); } } if (IsCompleted) { // Clear out state now that the async method has completed. // This avoids keeping arbitrary state referenced by lifted locals // if this Task / state machine box is held onto. StateMachine = default; Context = default; #if !CORERT // In case this is a state machine box with a finalizer, suppress its finalization // as it's now complete. We only need the finalizer to run if the box is collected // without having been completed. if (AsyncMethodBuilderCore.TrackAsyncMethodCompletion) { GC.SuppressFinalize(this); } #endif } if (loggingOn) { AsyncCausalityTracer.TraceSynchronousWorkCompletion(CausalitySynchronousWork.Execution); } } /// Gets the state machine as a boxed object. This should only be used for debugging purposes. IAsyncStateMachine IAsyncStateMachineBox.GetStateMachineObject() => StateMachine; // likely boxes, only use for debugging } /// Gets the for this builder. /// The representing the builder's asynchronous operation. public Task Task { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => m_task ?? InitializeTaskAsPromise(); } /// /// Initializes the task, which must not yet be initialized. Used only when the Task is being forced into /// existence when no state machine is needed, e.g. when the builder is being synchronously completed with /// an exception, when the builder is being used out of the context of an async method, etc. /// [MethodImpl(MethodImplOptions.NoInlining)] private Task InitializeTaskAsPromise() { Debug.Assert(m_task == null); return (m_task = new Task()); } /// /// Initializes the task, which must not yet be initialized. Used only when the Task is being forced into /// existence due to the debugger trying to enable step-out/step-over/etc. prior to the first await yielding /// in an async method. In that case, we don't know the actual TStateMachine type, so we're forced to /// use IAsyncStateMachine instead. /// [MethodImpl(MethodImplOptions.NoInlining)] private Task InitializeTaskAsStateMachineBox() { Debug.Assert(m_task == null); #if CORERT // DebugFinalizableAsyncStateMachineBox looks like a small type, but it actually is not because // it will have a copy of all the slots from its parent. It will add another hundred(s) bytes // per each async method in CoreRT / ProjectN binaries without adding much value. Avoid // generating this extra code until a better solution is implemented. return (m_task = new AsyncStateMachineBox()); #else return (m_task = AsyncMethodBuilderCore.TrackAsyncMethodCompletion ? CreateDebugFinalizableAsyncStateMachineBox() : new AsyncStateMachineBox()); #endif } /// /// Completes the in the /// RanToCompletion state with the specified result. /// /// The result to use to complete the task. /// The task has already completed. public void SetResult(TResult result) { // Get the currently stored task, which will be non-null if get_Task has already been accessed. // If there isn't one, get a task and store it. if (m_task == null) { m_task = GetTaskForResult(result); Debug.Assert(m_task != null, $"{nameof(GetTaskForResult)} should never return null"); } else { // Slow path: complete the existing task. SetExistingTaskResult(result); } } /// Completes the already initialized task with the specified result. /// The result to use to complete the task. private void SetExistingTaskResult(TResult result) { Debug.Assert(m_task != null, "Expected non-null task"); if (AsyncCausalityTracer.LoggingOn || System.Threading.Tasks.Task.s_asyncDebuggingEnabled) { LogExistingTaskCompletion(); } if (!m_task.TrySetResult(result)) { ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted); } } /// Handles logging for the successful completion of an operation. private void LogExistingTaskCompletion() { Debug.Assert(m_task != null); if (AsyncCausalityTracer.LoggingOn) { AsyncCausalityTracer.TraceOperationCompletion(m_task, AsyncCausalityStatus.Completed); } // only log if we have a real task that was previously created if (System.Threading.Tasks.Task.s_asyncDebuggingEnabled) { System.Threading.Tasks.Task.RemoveFromActiveTasks(m_task); } } /// /// Completes the builder by using either the supplied completed task, or by completing /// the builder's previously accessed task using default(TResult). /// /// A task already completed with the value default(TResult). /// The task has already completed. internal void SetResult(Task completedTask) { Debug.Assert(completedTask != null, "Expected non-null task"); Debug.Assert(completedTask.IsCompletedSuccessfully, "Expected a successfully completed task"); // Get the currently stored task, which will be non-null if get_Task has already been accessed. // If there isn't one, store the supplied completed task. if (m_task == null) { m_task = completedTask; } else { // Otherwise, complete the task that's there. SetExistingTaskResult(default); } } /// /// Completes the in the /// Faulted state with the specified exception. /// /// The to use to fault the task. /// The argument is null (Nothing in Visual Basic). /// The task has already completed. public void SetException(Exception exception) { if (exception == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exception); } // Get the task, forcing initialization if it hasn't already been initialized. Task task = this.Task; // If the exception represents cancellation, cancel the task. Otherwise, fault the task. bool successfullySet = exception is OperationCanceledException oce ? task.TrySetCanceled(oce.CancellationToken, oce) : task.TrySetException(exception); // Unlike with TaskCompletionSource, we do not need to spin here until _taskAndStateMachine is completed, // since AsyncTaskMethodBuilder.SetException should not be immediately followed by any code // that depends on the task having completely completed. Moreover, with correct usage, // SetResult or SetException should only be called once, so the Try* methods should always // return true, so no spinning would be necessary anyway (the spinning in TCS is only relevant // if another thread completes the task first). if (!successfullySet) { ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted); } } /// /// Called by the debugger to request notification when the first wait operation /// (await, Wait, Result, etc.) on this builder's task completes. /// /// /// true to enable notification; false to disable a previously set notification. /// /// /// This should only be invoked from within an asynchronous method, /// and only by the debugger. /// internal void SetNotificationForWaitCompletion(bool enabled) { // Get the task (forcing initialization if not already initialized), and set debug notification (m_task ?? InitializeTaskAsStateMachineBox()).SetNotificationForWaitCompletion(enabled); // NOTE: It's important that the debugger use builder.SetNotificationForWaitCompletion // rather than builder.Task.SetNotificationForWaitCompletion. Even though the latter will // lazily-initialize the task as well, it'll initialize it to a Task (which is important // to minimize size for cases where an ATMB is used directly by user code to avoid the // allocation overhead of a TaskCompletionSource). If that's done prior to the first await, // the GetMoveNextDelegate code, which needs an AsyncStateMachineBox, will end up creating // a new box and overwriting the previously created task. That'll change the object identity // of the task being used for wait completion notification, and no notification will // ever arrive, breaking step-out behavior when stepping out before the first yielding await. } /// /// Gets an object that may be used to uniquely identify this builder to the debugger. /// /// /// This property lazily instantiates the ID in a non-thread-safe manner. /// It must only be used by the debugger and tracing purposes, and only in a single-threaded manner /// when no other threads are in the middle of accessing this or other members that lazily initialize the task. /// internal object ObjectIdForDebugger => m_task ?? InitializeTaskAsStateMachineBox(); /// /// Gets a task for the specified result. This will either /// be a cached or new task, never null. /// /// The result for which we need a task. /// The completed task containing the result. [MethodImpl(MethodImplOptions.AggressiveInlining)] // method looks long, but for a given TResult it results in a relatively small amount of asm internal static Task GetTaskForResult(TResult result) { #if PROJECTN // Currently NUTC does not perform the optimization needed by this method. The result is that // every call to this method results in quite a lot of work, including many allocations, which // is the opposite of the intent. For now, let's just return a new Task each time. // Bug 719350 tracks re-optimizing this in ProjectN. #else // The goal of this function is to be give back a cached task if possible, // or to otherwise give back a new task. To give back a cached task, // we need to be able to evaluate the incoming result value, and we need // to avoid as much overhead as possible when doing so, as this function // is invoked as part of the return path from every async method. // Most tasks won't be cached, and thus we need the checks for those that are // to be as close to free as possible. This requires some trickiness given the // lack of generic specialization in .NET. // // Be very careful when modifying this code. It has been tuned // to comply with patterns recognized by both 32-bit and 64-bit JITs. // If changes are made here, be sure to look at the generated assembly, as // small tweaks can have big consequences for what does and doesn't get optimized away. // // Note that this code only ever accesses a static field when it knows it'll // find a cached value, since static fields (even if readonly and integral types) // require special access helpers in this NGEN'd and domain-neutral. if (null != (object)default(TResult)) // help the JIT avoid the value type branches for ref types { // Special case simple value types: // - Boolean // - Byte, SByte // - Char // - Int32, UInt32 // - Int64, UInt64 // - Int16, UInt16 // - IntPtr, UIntPtr // As of .NET 4.5, the (Type)(object)result pattern used below // is recognized and optimized by both 32-bit and 64-bit JITs. // For Boolean, we cache all possible values. if (typeof(TResult) == typeof(bool)) // only the relevant branches are kept for each value-type generic instantiation { bool value = (bool)(object)result; Task task = value ? AsyncTaskCache.TrueTask : AsyncTaskCache.FalseTask; return Unsafe.As>(task); // UnsafeCast avoids type check we know will succeed } // For Int32, we cache a range of common values, e.g. [-1,9). else if (typeof(TResult) == typeof(int)) { // Compare to constants to avoid static field access if outside of cached range. // We compare to the upper bound first, as we're more likely to cache miss on the upper side than on the // lower side, due to positive values being more common than negative as return values. int value = (int)(object)result; if (value < AsyncTaskCache.EXCLUSIVE_INT32_MAX && value >= AsyncTaskCache.INCLUSIVE_INT32_MIN) { Task task = AsyncTaskCache.Int32Tasks[value - AsyncTaskCache.INCLUSIVE_INT32_MIN]; return Unsafe.As>(task); // UnsafeCast avoids a type check we know will succeed } } // For other known value types, we only special-case 0 / default(TResult). else if ( (typeof(TResult) == typeof(uint) && default == (uint)(object)result) || (typeof(TResult) == typeof(byte) && default(byte) == (byte)(object)result) || (typeof(TResult) == typeof(sbyte) && default(sbyte) == (sbyte)(object)result) || (typeof(TResult) == typeof(char) && default(char) == (char)(object)result) || (typeof(TResult) == typeof(long) && default == (long)(object)result) || (typeof(TResult) == typeof(ulong) && default == (ulong)(object)result) || (typeof(TResult) == typeof(short) && default(short) == (short)(object)result) || (typeof(TResult) == typeof(ushort) && default(ushort) == (ushort)(object)result) || (typeof(TResult) == typeof(IntPtr) && default == (IntPtr)(object)result) || (typeof(TResult) == typeof(UIntPtr) && default == (UIntPtr)(object)result)) { return s_defaultResultTask; } } else if (result == null) // optimized away for value types { return s_defaultResultTask; } #endif // No cached task is available. Manufacture a new one for this result. return new Task(result); } } /// Provides a cache of closed generic tasks for async methods. internal static class AsyncTaskCache { #if !PROJECTN // All static members are initialized inline to ensure type is beforefieldinit /// A cached Task{Boolean}.Result == true. internal readonly static Task TrueTask = CreateCacheableTask(true); /// A cached Task{Boolean}.Result == false. internal readonly static Task FalseTask = CreateCacheableTask(false); /// The cache of Task{Int32}. internal readonly static Task[] Int32Tasks = CreateInt32Tasks(); /// The minimum value, inclusive, for which we want a cached task. internal const int INCLUSIVE_INT32_MIN = -1; /// The maximum value, exclusive, for which we want a cached task. internal const int EXCLUSIVE_INT32_MAX = 9; /// Creates an array of cached tasks for the values in the range [INCLUSIVE_MIN,EXCLUSIVE_MAX). private static Task[] CreateInt32Tasks() { Debug.Assert(EXCLUSIVE_INT32_MAX >= INCLUSIVE_INT32_MIN, "Expected max to be at least min"); var tasks = new Task[EXCLUSIVE_INT32_MAX - INCLUSIVE_INT32_MIN]; for (int i = 0; i < tasks.Length; i++) { tasks[i] = CreateCacheableTask(i + INCLUSIVE_INT32_MIN); } return tasks; } #endif /// Creates a non-disposable task. /// Specifies the result type. /// The result for the task. /// The cacheable task. internal static Task CreateCacheableTask(TResult result) => new Task(false, result, (TaskCreationOptions)InternalTaskOptions.DoNotDispose, default); } /// /// An interface implemented by all instances, regardless of generics. /// internal interface IAsyncStateMachineBox { /// Move the state machine forward. void MoveNext(); /// /// Gets an action for moving forward the contained state machine. /// This will lazily-allocate the delegate as needed. /// Action MoveNextAction { get; } /// Gets the state machine as a boxed object. This should only be used for debugging purposes. IAsyncStateMachine GetStateMachineObject(); } /// Shared helpers for manipulating state related to async state machines. internal static class AsyncMethodBuilderCore // debugger depends on this exact name { /// 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) { ExecutionContext.RestoreChangedContextToThread(currentThread1, previousExecutionCtx1, currentExecutionCtx1); } } } #if !CORERT /// Gets whether we should be tracking async method completions for eventing. internal static bool TrackAsyncMethodCompletion { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => TplEtwProvider.Log.IsEnabled(EventLevel.Warning, TplEtwProvider.Keywords.AsyncMethod); } #endif /// Gets a description of the state of the state machine object, suitable for debug purposes. /// The state machine object. /// A description of the state machine. internal static string GetAsyncStateMachineDescription(IAsyncStateMachine stateMachine) { Debug.Assert(stateMachine != null); Type stateMachineType = stateMachine.GetType(); FieldInfo[] fields = stateMachineType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); var sb = new StringBuilder(); sb.AppendLine(stateMachineType.FullName); foreach (FieldInfo fi in fields) { sb.AppendLine($" {fi.Name}: {fi.GetValue(stateMachine)}"); } return sb.ToString(); } internal static Action CreateContinuationWrapper(Action continuation, Action invokeAction, Task innerTask) => new ContinuationWrapper(continuation, invokeAction, innerTask).Invoke; /// This helper routine is targeted by the debugger. Its purpose is to remove any delegate wrappers introduced by /// the framework that the debugger doesn't want to see. #if PROJECTN [DependencyReductionRoot] #endif internal static Action TryGetStateMachineForDebugger(Action action) // debugger depends on this exact name/signature { object target = action.Target; return target is IAsyncStateMachineBox sm ? sm.GetStateMachineObject().MoveNext : target is ContinuationWrapper cw ? TryGetStateMachineForDebugger(cw._continuation) : action; } internal static Task TryGetContinuationTask(Action continuation) => (continuation?.Target as ContinuationWrapper)?._innerTask; /// Throws the exception on the ThreadPool. /// The exception to propagate. /// The target context on which to propagate the exception. Null to use the ThreadPool. internal static void ThrowAsync(Exception exception, SynchronizationContext targetContext) { // Capture the exception into an ExceptionDispatchInfo so that its // stack trace and Watson bucket info will be preserved var edi = ExceptionDispatchInfo.Capture(exception); // If the user supplied a SynchronizationContext... if (targetContext != null) { try { // Post the throwing of the exception to that context, and return. targetContext.Post(state => ((ExceptionDispatchInfo)state).Throw(), edi); return; } catch (Exception postException) { // If something goes horribly wrong in the Post, we'll // propagate both exceptions on the ThreadPool edi = ExceptionDispatchInfo.Capture(new AggregateException(exception, postException)); } } #if CORERT RuntimeAugments.ReportUnhandledException(edi.SourceException); #else #if FEATURE_COMINTEROP // If we have the new error reporting APIs, report this error. if (WindowsRuntimeMarshal.ReportUnhandledError(edi.SourceException)) return; #endif // FEATURE_COMINTEROP // Propagate the exception(s) on the ThreadPool ThreadPool.QueueUserWorkItem(state => ((ExceptionDispatchInfo)state).Throw(), edi); #endif // CORERT } /// /// Logically we pass just an Action (delegate) to a task for its action to 'ContinueWith' when it completes. /// However debuggers and profilers need more information about what that action is. (In particular what /// the action after that is and after that. To solve this problem we create a 'ContinuationWrapper /// which when invoked just does the original action (the invoke action), but also remembers other information /// (like the action after that (which is also a ContinuationWrapper and thus form a linked list). /// We also store that task if the action is associate with at task. /// private sealed class ContinuationWrapper { private readonly Action _invokeAction; // This wrapper is an action that wraps another action, this is that Action. internal readonly Action _continuation; // This is continuation which will happen after m_invokeAction (and is probably a ContinuationWrapper) internal readonly Task _innerTask; // If the continuation is logically going to invoke a task, this is that task (may be null) internal ContinuationWrapper(Action continuation, Action invokeAction, Task innerTask) { Debug.Assert(continuation != null, "Expected non-null continuation"); Debug.Assert(invokeAction != null, "Expected non-null continuation"); _invokeAction = invokeAction; _continuation = continuation; _innerTask = innerTask ?? TryGetContinuationTask(continuation); // if we don't have a task, see if our continuation is a wrapper and use that. } internal void Invoke() => _invokeAction(_continuation, _innerTask); } } }