// 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. // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ // // // // Types for awaiting Task and Task. These types are emitted from Task{}.GetAwaiter // and Task{}.ConfigureAwait. They are meant to be used only by the compiler, e.g. // // await nonGenericTask; // ===================== // var $awaiter = nonGenericTask.GetAwaiter(); // if (!$awaiter.IsCompleted) // { // SPILL: // $builder.AwaitUnsafeOnCompleted(ref $awaiter, ref this); // return; // Label: // UNSPILL; // } // $awaiter.GetResult(); // // result += await genericTask.ConfigureAwait(false); // =================================================================================== // var $awaiter = genericTask.ConfigureAwait(false).GetAwaiter(); // if (!$awaiter.IsCompleted) // { // SPILL; // $builder.AwaitUnsafeOnCompleted(ref $awaiter, ref this); // return; // Label: // UNSPILL; // } // result += $awaiter.GetResult(); // // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Security; using System.Threading; using System.Threading.Tasks; using System.Diagnostics.Tracing; // NOTE: For performance reasons, initialization is not verified. If a developer // incorrectly initializes a task awaiter, which should only be done by the compiler, // NullReferenceExceptions may be generated (the alternative would be for us to detect // this case and then throw a different exception instead). This is the same tradeoff // that's made with other compiler-focused value types like List.Enumerator. namespace System.Runtime.CompilerServices { /// Provides an awaiter for awaiting a . /// This type is intended for compiler use only. public struct TaskAwaiter : ICriticalNotifyCompletion { /// The task being awaited. private readonly Task m_task; /// Initializes the . /// The to be awaited. internal TaskAwaiter(Task task) { Contract.Requires(task != null, "Constructing an awaiter requires a task to await."); m_task = task; } /// Gets whether the task being awaited is completed. /// This property is intended for compiler user rather than use directly in code. /// The awaiter was not properly initialized. public bool IsCompleted { get { return m_task.IsCompleted; } } /// Schedules the continuation onto the associated with this . /// The action to invoke when the await operation completes. /// The argument is null (Nothing in Visual Basic). /// The awaiter was not properly initialized. /// This method is intended for compiler user rather than use directly in code. public void OnCompleted(Action continuation) { OnCompletedInternal(m_task, continuation, continueOnCapturedContext: true, flowExecutionContext: true); } /// Schedules the continuation onto the associated with this . /// The action to invoke when the await operation completes. /// The argument is null (Nothing in Visual Basic). /// The awaiter was not properly initialized. /// This method is intended for compiler user rather than use directly in code. public void UnsafeOnCompleted(Action continuation) { OnCompletedInternal(m_task, continuation, continueOnCapturedContext: true, flowExecutionContext: false); } /// Ends the await on the completed . /// The awaiter was not properly initialized. /// The task was canceled. /// The task completed in a Faulted state. public void GetResult() { ValidateEnd(m_task); } /// /// Fast checks for the end of an await operation to determine whether more needs to be done /// prior to completing the await. /// /// The awaited task. internal static void ValidateEnd(Task task) { // Fast checks that can be inlined. if (task.IsWaitNotificationEnabledOrNotRanToCompletion) { // If either the end await bit is set or we're not completed successfully, // fall back to the slower path. HandleNonSuccessAndDebuggerNotification(task); } } /// /// Ensures the task is completed, triggers any necessary debugger breakpoints for completing /// the await on the task, and throws an exception if the task did not complete successfully. /// /// The awaited task. private static void HandleNonSuccessAndDebuggerNotification(Task task) { // NOTE: The JIT refuses to inline ValidateEnd when it contains the contents // of HandleNonSuccessAndDebuggerNotification, hence the separation. // Synchronously wait for the task to complete. When used by the compiler, // the task will already be complete. This code exists only for direct GetResult use, // for cases where the same exception propagation semantics used by "await" are desired, // but where for one reason or another synchronous rather than asynchronous waiting is needed. if (!task.IsCompleted) { bool taskCompleted = task.InternalWait(Timeout.Infinite, default(CancellationToken)); Debug.Assert(taskCompleted, "With an infinite timeout, the task should have always completed."); } // Now that we're done, alert the debugger if so requested task.NotifyDebuggerOfWaitCompletionIfNecessary(); // And throw an exception if the task is faulted or canceled. if (!task.IsCompletedSuccessfully) ThrowForNonSuccess(task); } /// Throws an exception to handle a task that completed in a state other than RanToCompletion. private static void ThrowForNonSuccess(Task task) { Contract.Requires(task.IsCompleted, "Task must have been completed by now."); Contract.Requires(task.Status != TaskStatus.RanToCompletion, "Task should not be completed successfully."); // Handle whether the task has been canceled or faulted switch (task.Status) { // If the task completed in a canceled state, throw an OperationCanceledException. // This will either be the OCE that actually caused the task to cancel, or it will be a new // TaskCanceledException. TCE derives from OCE, and by throwing it we automatically pick up the // completed task's CancellationToken if it has one, including that CT in the OCE. case TaskStatus.Canceled: var oceEdi = task.GetCancellationExceptionDispatchInfo(); if (oceEdi != null) { oceEdi.Throw(); Debug.Assert(false, "Throw() should have thrown"); } throw new TaskCanceledException(task); // If the task faulted, throw its first exception, // even if it contained more than one. case TaskStatus.Faulted: var edis = task.GetExceptionDispatchInfos(); if (edis.Count > 0) { edis[0].Throw(); Debug.Assert(false, "Throw() should have thrown"); break; // Necessary to compile: non-reachable, but compiler can't determine that } else { Debug.Assert(false, "There should be exceptions if we're Faulted."); throw task.Exception; } } } /// Schedules the continuation onto the associated with this . /// The task being awaited. /// The action to invoke when the await operation completes. /// Whether to capture and marshal back to the current context. /// Whether to flow ExecutionContext across the await. /// The argument is null (Nothing in Visual Basic). /// The awaiter was not properly initialized. /// This method is intended for compiler user rather than use directly in code. internal static void OnCompletedInternal(Task task, Action continuation, bool continueOnCapturedContext, bool flowExecutionContext) { if (continuation == null) throw new ArgumentNullException(nameof(continuation)); // If TaskWait* ETW events are enabled, trace a beginning event for this await // and set up an ending event to be traced when the asynchronous await completes. if (TplEtwProvider.Log.IsEnabled() || Task.s_asyncDebuggingEnabled) { continuation = OutputWaitEtwEvents(task, continuation); } // Set the continuation onto the awaited task. task.SetContinuationForAwait(continuation, continueOnCapturedContext, flowExecutionContext); } /// /// Outputs a WaitBegin ETW event, and augments the continuation action to output a WaitEnd ETW event. /// /// The task being awaited. /// The action to invoke when the await operation completes. /// The action to use as the actual continuation. private static Action OutputWaitEtwEvents(Task task, Action continuation) { Contract.Requires(task != null, "Need a task to wait on"); Contract.Requires(continuation != null, "Need a continuation to invoke when the wait completes"); if (Task.s_asyncDebuggingEnabled) { Task.AddToActiveTasks(task); } var etwLog = TplEtwProvider.Log; if (etwLog.IsEnabled()) { // ETW event for Task Wait Begin var currentTaskAtBegin = Task.InternalCurrent; // If this task's continuation is another task, get it. var continuationTask = AsyncMethodBuilderCore.TryGetContinuationTask(continuation); etwLog.TaskWaitBegin( (currentTaskAtBegin != null ? currentTaskAtBegin.m_taskScheduler.Id : TaskScheduler.Default.Id), (currentTaskAtBegin != null ? currentTaskAtBegin.Id : 0), task.Id, TplEtwProvider.TaskWaitBehavior.Asynchronous, (continuationTask != null ? continuationTask.Id : 0)); } // Create a continuation action that outputs the end event and then invokes the user // provided delegate. This incurs the allocations for the closure/delegate, but only if the event // is enabled, and in doing so it allows us to pass the awaited task's information into the end event // in a purely pay-for-play manner (the alternatively would be to increase the size of TaskAwaiter // just for this ETW purpose, not pay-for-play, since GetResult would need to know whether a real yield occurred). return AsyncMethodBuilderCore.CreateContinuationWrapper(continuation, () => { if (Task.s_asyncDebuggingEnabled) { Task.RemoveFromActiveTasks(task.Id); } // ETW event for Task Wait End. Guid prevActivityId = new Guid(); bool bEtwLogEnabled = etwLog.IsEnabled(); if (bEtwLogEnabled) { var currentTaskAtEnd = Task.InternalCurrent; etwLog.TaskWaitEnd( (currentTaskAtEnd != null ? currentTaskAtEnd.m_taskScheduler.Id : TaskScheduler.Default.Id), (currentTaskAtEnd != null ? currentTaskAtEnd.Id : 0), task.Id); // Ensure the continuation runs under the activity ID of the task that completed for the // case the antecendent is a promise (in the other cases this is already the case). if (etwLog.TasksSetActivityIds && (task.Options & (TaskCreationOptions)InternalTaskOptions.PromiseTask) != 0) EventSource.SetCurrentThreadActivityId(TplEtwProvider.CreateGuidForTaskID(task.Id), out prevActivityId); } // Invoke the original continuation provided to OnCompleted. continuation(); if (bEtwLogEnabled) { etwLog.TaskWaitContinuationComplete(task.Id); if (etwLog.TasksSetActivityIds && (task.Options & (TaskCreationOptions)InternalTaskOptions.PromiseTask) != 0) EventSource.SetCurrentThreadActivityId(prevActivityId); } }); } } /// Provides an awaiter for awaiting a . /// This type is intended for compiler use only. public struct TaskAwaiter : ICriticalNotifyCompletion { /// The task being awaited. private readonly Task m_task; /// Initializes the . /// The to be awaited. internal TaskAwaiter(Task task) { Contract.Requires(task != null, "Constructing an awaiter requires a task to await."); m_task = task; } /// Gets whether the task being awaited is completed. /// This property is intended for compiler user rather than use directly in code. /// The awaiter was not properly initialized. public bool IsCompleted { get { return m_task.IsCompleted; } } /// Schedules the continuation onto the associated with this . /// The action to invoke when the await operation completes. /// The argument is null (Nothing in Visual Basic). /// The awaiter was not properly initialized. /// This method is intended for compiler user rather than use directly in code. public void OnCompleted(Action continuation) { TaskAwaiter.OnCompletedInternal(m_task, continuation, continueOnCapturedContext: true, flowExecutionContext: true); } /// Schedules the continuation onto the associated with this . /// The action to invoke when the await operation completes. /// The argument is null (Nothing in Visual Basic). /// The awaiter was not properly initialized. /// This method is intended for compiler user rather than use directly in code. public void UnsafeOnCompleted(Action continuation) { TaskAwaiter.OnCompletedInternal(m_task, continuation, continueOnCapturedContext: true, flowExecutionContext: false); } /// Ends the await on the completed . /// The result of the completed . /// The awaiter was not properly initialized. /// The task was canceled. /// The task completed in a Faulted state. public TResult GetResult() { TaskAwaiter.ValidateEnd(m_task); return m_task.ResultOnSuccess; } } /// Provides an awaitable object that allows for configured awaits on . /// This type is intended for compiler use only. public struct ConfiguredTaskAwaitable { /// The task being awaited. private readonly ConfiguredTaskAwaitable.ConfiguredTaskAwaiter m_configuredTaskAwaiter; /// Initializes the . /// The awaitable . /// /// true to attempt to marshal the continuation back to the original context captured; otherwise, false. /// internal ConfiguredTaskAwaitable(Task task, bool continueOnCapturedContext) { Contract.Requires(task != null, "Constructing an awaitable requires a task to await."); m_configuredTaskAwaiter = new ConfiguredTaskAwaitable.ConfiguredTaskAwaiter(task, continueOnCapturedContext); } /// Gets an awaiter for this awaitable. /// The awaiter. public ConfiguredTaskAwaitable.ConfiguredTaskAwaiter GetAwaiter() { return m_configuredTaskAwaiter; } /// Provides an awaiter for a . /// This type is intended for compiler use only. public struct ConfiguredTaskAwaiter : ICriticalNotifyCompletion { /// The task being awaited. private readonly Task m_task; /// Whether to attempt marshaling back to the original context. private readonly bool m_continueOnCapturedContext; /// Initializes the . /// The to await. /// /// true to attempt to marshal the continuation back to the original context captured /// when BeginAwait is called; otherwise, false. /// internal ConfiguredTaskAwaiter(Task task, bool continueOnCapturedContext) { Contract.Requires(task != null, "Constructing an awaiter requires a task to await."); m_task = task; m_continueOnCapturedContext = continueOnCapturedContext; } /// Gets whether the task being awaited is completed. /// This property is intended for compiler user rather than use directly in code. /// The awaiter was not properly initialized. public bool IsCompleted { get { return m_task.IsCompleted; } } /// Schedules the continuation onto the associated with this . /// The action to invoke when the await operation completes. /// The argument is null (Nothing in Visual Basic). /// The awaiter was not properly initialized. /// This method is intended for compiler user rather than use directly in code. public void OnCompleted(Action continuation) { TaskAwaiter.OnCompletedInternal(m_task, continuation, m_continueOnCapturedContext, flowExecutionContext: true); } /// Schedules the continuation onto the associated with this . /// The action to invoke when the await operation completes. /// The argument is null (Nothing in Visual Basic). /// The awaiter was not properly initialized. /// This method is intended for compiler user rather than use directly in code. public void UnsafeOnCompleted(Action continuation) { TaskAwaiter.OnCompletedInternal(m_task, continuation, m_continueOnCapturedContext, flowExecutionContext: false); } /// Ends the await on the completed . /// The result of the completed . /// The awaiter was not properly initialized. /// The task was canceled. /// The task completed in a Faulted state. public void GetResult() { TaskAwaiter.ValidateEnd(m_task); } } } /// Provides an awaitable object that allows for configured awaits on . /// This type is intended for compiler use only. public struct ConfiguredTaskAwaitable { /// The underlying awaitable on whose logic this awaitable relies. private readonly ConfiguredTaskAwaitable.ConfiguredTaskAwaiter m_configuredTaskAwaiter; /// Initializes the . /// The awaitable . /// /// true to attempt to marshal the continuation back to the original context captured; otherwise, false. /// internal ConfiguredTaskAwaitable(Task task, bool continueOnCapturedContext) { m_configuredTaskAwaiter = new ConfiguredTaskAwaitable.ConfiguredTaskAwaiter(task, continueOnCapturedContext); } /// Gets an awaiter for this awaitable. /// The awaiter. public ConfiguredTaskAwaitable.ConfiguredTaskAwaiter GetAwaiter() { return m_configuredTaskAwaiter; } /// Provides an awaiter for a . /// This type is intended for compiler use only. public struct ConfiguredTaskAwaiter : ICriticalNotifyCompletion { /// The task being awaited. private readonly Task m_task; /// Whether to attempt marshaling back to the original context. private readonly bool m_continueOnCapturedContext; /// Initializes the . /// The awaitable . /// /// true to attempt to marshal the continuation back to the original context captured; otherwise, false. /// internal ConfiguredTaskAwaiter(Task task, bool continueOnCapturedContext) { Contract.Requires(task != null, "Constructing an awaiter requires a task to await."); m_task = task; m_continueOnCapturedContext = continueOnCapturedContext; } /// Gets whether the task being awaited is completed. /// This property is intended for compiler user rather than use directly in code. /// The awaiter was not properly initialized. public bool IsCompleted { get { return m_task.IsCompleted; } } /// Schedules the continuation onto the associated with this . /// The action to invoke when the await operation completes. /// The argument is null (Nothing in Visual Basic). /// The awaiter was not properly initialized. /// This method is intended for compiler user rather than use directly in code. public void OnCompleted(Action continuation) { TaskAwaiter.OnCompletedInternal(m_task, continuation, m_continueOnCapturedContext, flowExecutionContext: true); } /// Schedules the continuation onto the associated with this . /// The action to invoke when the await operation completes. /// The argument is null (Nothing in Visual Basic). /// The awaiter was not properly initialized. /// This method is intended for compiler user rather than use directly in code. public void UnsafeOnCompleted(Action continuation) { TaskAwaiter.OnCompletedInternal(m_task, continuation, m_continueOnCapturedContext, flowExecutionContext: false); } /// Ends the await on the completed . /// The result of the completed . /// The awaiter was not properly initialized. /// The task was canceled. /// The task completed in a Faulted state. public TResult GetResult() { TaskAwaiter.ValidateEnd(m_task); return m_task.ResultOnSuccess; } } } }