// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; using System.Runtime.CompilerServices; namespace System.Threading.Tasks { // Task type used to implement: Task ContinueWith(Action<Task,...>) internal sealed class ContinuationTaskFromTask : Task { private Task? m_antecedent; public ContinuationTaskFromTask( Task antecedent, Delegate action, object? state, TaskCreationOptions creationOptions, InternalTaskOptions internalOptions) : base(action, state, InternalCurrentIfAttached(creationOptions), default, creationOptions, internalOptions, null) { Debug.Assert(action is Action<Task> || action is Action<Task, object?>, "Invalid delegate type in ContinuationTaskFromTask"); m_antecedent = antecedent; } /// <summary> /// Evaluates the value selector of the Task which is passed in as an object and stores the result. /// </summary> internal override void InnerInvoke() { // Get and null out the antecedent. This is crucial to avoid a memory // leak with long chains of continuations. Task? antecedent = m_antecedent; Debug.Assert(antecedent != null, "No antecedent was set for the ContinuationTaskFromTask."); m_antecedent = null; // Notify the debugger we're completing an asynchronous wait on a task antecedent.NotifyDebuggerOfWaitCompletionIfNecessary(); // Invoke the delegate Debug.Assert(m_action != null); if (m_action is Action<Task> action) { action(antecedent); return; } if (m_action is Action<Task, object?> actionWithState) { actionWithState(antecedent, m_stateObject); return; } Debug.Fail("Invalid m_action in ContinuationTaskFromTask"); } } // Task type used to implement: Task<TResult> ContinueWith(Func<Task,...>) internal sealed class ContinuationResultTaskFromTask<TResult> : Task<TResult> { private Task? m_antecedent; public ContinuationResultTaskFromTask( Task antecedent, Delegate function, object? state, TaskCreationOptions creationOptions, InternalTaskOptions internalOptions) : base(function, state, InternalCurrentIfAttached(creationOptions), default, creationOptions, internalOptions, null) { Debug.Assert(function is Func<Task, TResult> || function is Func<Task, object?, TResult>, "Invalid delegate type in ContinuationResultTaskFromTask"); m_antecedent = antecedent; } /// <summary> /// Evaluates the value selector of the Task which is passed in as an object and stores the result. /// </summary> internal override void InnerInvoke() { // Get and null out the antecedent. This is crucial to avoid a memory // leak with long chains of continuations. Task? antecedent = m_antecedent; Debug.Assert(antecedent != null, "No antecedent was set for the ContinuationResultTaskFromTask."); m_antecedent = null; // Notify the debugger we're completing an asynchronous wait on a task antecedent.NotifyDebuggerOfWaitCompletionIfNecessary(); // Invoke the delegate Debug.Assert(m_action != null); if (m_action is Func<Task, TResult> func) { m_result = func(antecedent); return; } if (m_action is Func<Task, object?, TResult> funcWithState) { m_result = funcWithState(antecedent, m_stateObject); return; } Debug.Fail("Invalid m_action in ContinuationResultTaskFromTask"); } } // Task type used to implement: Task ContinueWith(Action<Task<TAntecedentResult>,...>) internal sealed class ContinuationTaskFromResultTask<TAntecedentResult> : Task { private Task<TAntecedentResult>? m_antecedent; public ContinuationTaskFromResultTask( Task<TAntecedentResult> antecedent, Delegate action, object? state, TaskCreationOptions creationOptions, InternalTaskOptions internalOptions) : base(action, state, InternalCurrentIfAttached(creationOptions), default, creationOptions, internalOptions, null) { Debug.Assert(action is Action<Task<TAntecedentResult>> || action is Action<Task<TAntecedentResult>, object?>, "Invalid delegate type in ContinuationTaskFromResultTask"); m_antecedent = antecedent; } /// <summary> /// Evaluates the value selector of the Task which is passed in as an object and stores the result. /// </summary> internal override void InnerInvoke() { // Get and null out the antecedent. This is crucial to avoid a memory // leak with long chains of continuations. Task<TAntecedentResult>? antecedent = m_antecedent; Debug.Assert(antecedent != null, "No antecedent was set for the ContinuationTaskFromResultTask."); m_antecedent = null; // Notify the debugger we're completing an asynchronous wait on a task antecedent.NotifyDebuggerOfWaitCompletionIfNecessary(); // Invoke the delegate Debug.Assert(m_action != null); if (m_action is Action<Task<TAntecedentResult>> action) { action(antecedent); return; } if (m_action is Action<Task<TAntecedentResult>, object?> actionWithState) { actionWithState(antecedent, m_stateObject); return; } Debug.Fail("Invalid m_action in ContinuationTaskFromResultTask"); } } // Task type used to implement: Task<TResult> ContinueWith(Func<Task<TAntecedentResult>,...>) internal sealed class ContinuationResultTaskFromResultTask<TAntecedentResult, TResult> : Task<TResult> { private Task<TAntecedentResult>? m_antecedent; public ContinuationResultTaskFromResultTask( Task<TAntecedentResult> antecedent, Delegate function, object? state, TaskCreationOptions creationOptions, InternalTaskOptions internalOptions) : base(function, state, InternalCurrentIfAttached(creationOptions), default, creationOptions, internalOptions, null) { Debug.Assert(function is Func<Task<TAntecedentResult>, TResult> || function is Func<Task<TAntecedentResult>, object?, TResult>, "Invalid delegate type in ContinuationResultTaskFromResultTask"); m_antecedent = antecedent; } /// <summary> /// Evaluates the value selector of the Task which is passed in as an object and stores the result. /// </summary> internal override void InnerInvoke() { // Get and null out the antecedent. This is crucial to avoid a memory // leak with long chains of continuations. Task<TAntecedentResult>? antecedent = m_antecedent; Debug.Assert(antecedent != null, "No antecedent was set for the ContinuationResultTaskFromResultTask."); m_antecedent = null; // Notify the debugger we're completing an asynchronous wait on a task antecedent.NotifyDebuggerOfWaitCompletionIfNecessary(); // Invoke the delegate Debug.Assert(m_action != null); if (m_action is Func<Task<TAntecedentResult>, TResult> func) { m_result = func(antecedent); return; } if (m_action is Func<Task<TAntecedentResult>, object?, TResult> funcWithState) { m_result = funcWithState(antecedent, m_stateObject); return; } Debug.Fail("Invalid m_action in ContinuationResultTaskFromResultTask"); } } // For performance reasons, we don't just have a single way of representing // a continuation object. Rather, we have a hierarchy of types: // - TaskContinuation: abstract base that provides a virtual Run method // - StandardTaskContinuation: wraps a task,options,and scheduler, and overrides Run to process the task with that configuration // - AwaitTaskContinuation: base for continuations created through TaskAwaiter; targets default scheduler by default // - TaskSchedulerAwaitTaskContinuation: awaiting with a non-default TaskScheduler // - SynchronizationContextAwaitTaskContinuation: awaiting with a "current" sync ctx /// <summary>Represents a continuation.</summary> internal abstract class TaskContinuation { /// <summary>Inlines or schedules the continuation.</summary> /// <param name="completedTask">The antecedent task that has completed.</param> /// <param name="canInlineContinuationTask">true if inlining is permitted; otherwise, false.</param> internal abstract void Run(Task completedTask, bool canInlineContinuationTask); /// <summary>Tries to run the task on the current thread, if possible; otherwise, schedules it.</summary> /// <param name="task">The task to run</param> /// <param name="needsProtection"> /// true if we need to protect against multiple threads racing to start/cancel the task; otherwise, false. /// </param> protected static void InlineIfPossibleOrElseQueue(Task task, bool needsProtection) { Debug.Assert(task != null); Debug.Assert(task.m_taskScheduler != null); // Set the TaskStateFlags.Started flag. This only needs to be done // if the task may be canceled or if someone else has a reference to it // that may try to execute it. if (needsProtection) { if (!task.MarkStarted()) return; // task has been previously started or canceled. Stop processing. } else { task.m_stateFlags |= (int)Task.TaskStateFlags.Started; } // Try to inline it but queue if we can't try { if (!task.m_taskScheduler.TryRunInline(task, taskWasPreviouslyQueued: false)) { task.m_taskScheduler.InternalQueueTask(task); } } catch (Exception e) { // Either TryRunInline() or QueueTask() threw an exception. Record the exception, marking the task as Faulted. // However if it was a ThreadAbortException coming from TryRunInline we need to skip here, // because it would already have been handled in Task.Execute() TaskSchedulerException tse = new TaskSchedulerException(e); task.AddException(tse); task.Finish(false); // Don't re-throw. } } // // This helper routine is targeted by the debugger. // internal abstract Delegate[]? GetDelegateContinuationsForDebugger(); } /// <summary>Provides the standard implementation of a task continuation.</summary> internal sealed class ContinueWithTaskContinuation : TaskContinuation { /// <summary>The unstarted continuation task.</summary> internal Task? m_task; /// <summary>The options to use with the continuation task.</summary> internal readonly TaskContinuationOptions m_options; /// <summary>The task scheduler with which to run the continuation task.</summary> private readonly TaskScheduler m_taskScheduler; /// <summary>Initializes a new continuation.</summary> /// <param name="task">The task to be activated.</param> /// <param name="options">The continuation options.</param> /// <param name="scheduler">The scheduler to use for the continuation.</param> internal ContinueWithTaskContinuation(Task task, TaskContinuationOptions options, TaskScheduler scheduler) { Debug.Assert(task != null, "TaskContinuation ctor: task is null"); Debug.Assert(scheduler != null, "TaskContinuation ctor: scheduler is null"); m_task = task; m_options = options; m_taskScheduler = scheduler; if (TplEventSource.Log.IsEnabled()) TplEventSource.Log.TraceOperationBegin(m_task.Id, "Task.ContinueWith: " + task.m_action!.GetMethodName(), 0); if (Task.s_asyncDebuggingEnabled) Task.AddToActiveTasks(m_task); } /// <summary>Invokes the continuation for the target completion task.</summary> /// <param name="completedTask">The completed task.</param> /// <param name="canInlineContinuationTask">Whether the continuation can be inlined.</param> internal override void Run(Task completedTask, bool canInlineContinuationTask) { Debug.Assert(completedTask != null); Debug.Assert(completedTask.IsCompleted, "ContinuationTask.Run(): completedTask not completed"); Task? continuationTask = m_task; Debug.Assert(continuationTask != null); m_task = null; // Check if the completion status of the task works with the desired // activation criteria of the TaskContinuationOptions. TaskContinuationOptions options = m_options; bool isRightKind = completedTask.IsCompletedSuccessfully ? (options & TaskContinuationOptions.NotOnRanToCompletion) == 0 : (completedTask.IsCanceled ? (options & TaskContinuationOptions.NotOnCanceled) == 0 : (options & TaskContinuationOptions.NotOnFaulted) == 0); // If the completion status is allowed, run the continuation. if (isRightKind) { // If the task was cancel before running (e.g a ContinueWhenAll with a cancelled caancelation token) // we will still flow it to ScheduleAndStart() were it will check the status before running // We check here to avoid faulty logs that contain a join event to an operation that was already set as completed. if (TplEventSource.Log.IsEnabled() && !continuationTask.IsCanceled) { // Log now that we are sure that this continuation is being ran TplEventSource.Log.TraceOperationRelation(continuationTask.Id, CausalityRelation.AssignDelegate); } continuationTask.m_taskScheduler = m_taskScheduler; // Either run directly or just queue it up for execution, depending // on whether synchronous or asynchronous execution is wanted. if (canInlineContinuationTask && // inlining is allowed by the caller (options & TaskContinuationOptions.ExecuteSynchronously) != 0) // synchronous execution was requested by the continuation's creator { InlineIfPossibleOrElseQueue(continuationTask, needsProtection: true); } else { try { continuationTask.ScheduleAndStart(needsProtection: true); } catch (TaskSchedulerException) { // No further action is necessary -- ScheduleAndStart() already transitioned the // task to faulted. But we want to make sure that no exception is thrown from here. } } } else { // Otherwise, the final state of this task does not match the desired continuation activation criteria; cancel it to denote this. Task.ContingentProperties? cp = continuationTask.m_contingentProperties; // no need to volatile read, as we only care about the token, which is only assignable at construction if (cp is null || cp.m_cancellationToken == default) { // With no cancellation token, use an optimized path that doesn't need to account for concurrent completion. // This is primarily valuable for continuations created with TaskContinuationOptions.NotOn* options, where // we'll cancel the continuation if it's not needed. continuationTask.InternalCancelContinueWithInitialState(); } else { // There's a non-default token. Follow the normal internal cancellation path. continuationTask.InternalCancel(); } } } internal override Delegate[]? GetDelegateContinuationsForDebugger() => m_task is null ? null : m_task.m_action is null ? m_task.GetDelegateContinuationsForDebugger() : [m_task.m_action]; } /// <summary>Task continuation for awaiting with a current synchronization context.</summary> internal sealed class SynchronizationContextAwaitTaskContinuation : AwaitTaskContinuation { /// <summary>SendOrPostCallback delegate to invoke the action.</summary> private static readonly SendOrPostCallback s_postCallback = static state => { Debug.Assert(state is Action); ((Action)state)(); }; /// <summary>Cached delegate for PostAction</summary> private static ContextCallback? s_postActionCallback; /// <summary>The context with which to run the action.</summary> private readonly SynchronizationContext m_syncContext; /// <summary>Initializes the SynchronizationContextAwaitTaskContinuation.</summary> /// <param name="context">The synchronization context with which to invoke the action. Must not be null.</param> /// <param name="action">The action to invoke. Must not be null.</param> /// <param name="flowExecutionContext">Whether to capture and restore ExecutionContext.</param> internal SynchronizationContextAwaitTaskContinuation( SynchronizationContext context, Action action, bool flowExecutionContext) : base(action, flowExecutionContext) { Debug.Assert(context != null); m_syncContext = context; } /// <summary>Inlines or schedules the continuation.</summary> /// <param name="task">The antecedent task, which is ignored.</param> /// <param name="canInlineContinuationTask">true if inlining is permitted; otherwise, false.</param> internal sealed override void Run(Task task, bool canInlineContinuationTask) { // If we're allowed to inline, run the action on this thread. if (canInlineContinuationTask && m_syncContext == SynchronizationContext.Current) { RunCallback(GetInvokeActionCallback(), m_action, ref Task.t_currentTask); } // Otherwise, Post the action back to the SynchronizationContext. else { TplEventSource log = TplEventSource.Log; if (log.IsEnabled()) { m_continuationId = Task.NewId(); log.AwaitTaskContinuationScheduled((task.ExecutingTaskScheduler ?? TaskScheduler.Default).Id, task.Id, m_continuationId); } RunCallback(GetPostActionCallback(), this, ref Task.t_currentTask); } // Any exceptions will be handled by RunCallback. } /// <summary>Calls InvokeOrPostAction(false) on the supplied SynchronizationContextAwaitTaskContinuation.</summary> /// <param name="state">The SynchronizationContextAwaitTaskContinuation.</param> private static void PostAction(object? state) { Debug.Assert(state is SynchronizationContextAwaitTaskContinuation); var c = (SynchronizationContextAwaitTaskContinuation)state; TplEventSource log = TplEventSource.Log; if (log.IsEnabled() && log.TasksSetActivityIds && c.m_continuationId != 0) { c.m_syncContext.Post(s_postCallback, GetActionLogDelegate(c.m_continuationId, c.m_action)); } else { c.m_syncContext.Post(s_postCallback, c.m_action); // s_postCallback is manually cached, as the compiler won't in a SecurityCritical method } } private static Action GetActionLogDelegate(int continuationId, Action action) { return () => { Guid activityId = TplEventSource.CreateGuidForTaskID(continuationId); Diagnostics.Tracing.EventSource.SetCurrentThreadActivityId(activityId, out Guid savedActivityId); try { action(); } finally { Diagnostics.Tracing.EventSource.SetCurrentThreadActivityId(savedActivityId); } }; } /// <summary>Gets a cached delegate for the PostAction method.</summary> /// <returns> /// A delegate for PostAction, which expects a SynchronizationContextAwaitTaskContinuation /// to be passed as state. /// </returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ContextCallback GetPostActionCallback() => s_postActionCallback ??= PostAction; } /// <summary>Task continuation for awaiting with a task scheduler.</summary> internal sealed class TaskSchedulerAwaitTaskContinuation : AwaitTaskContinuation { /// <summary>The scheduler on which to run the action.</summary> private readonly TaskScheduler m_scheduler; /// <summary>Initializes the TaskSchedulerAwaitTaskContinuation.</summary> /// <param name="scheduler">The task scheduler with which to invoke the action. Must not be null.</param> /// <param name="action">The action to invoke. Must not be null.</param> /// <param name="flowExecutionContext">Whether to capture and restore ExecutionContext.</param> internal TaskSchedulerAwaitTaskContinuation( TaskScheduler scheduler, Action action, bool flowExecutionContext) : base(action, flowExecutionContext) { Debug.Assert(scheduler != null); m_scheduler = scheduler; } /// <summary>Inlines or schedules the continuation.</summary> /// <param name="ignored">The antecedent task, which is ignored.</param> /// <param name="canInlineContinuationTask">true if inlining is permitted; otherwise, false.</param> internal sealed override void Run(Task ignored, bool canInlineContinuationTask) { // If we're targeting the default scheduler, we can use the faster path provided by the base class. if (m_scheduler == TaskScheduler.Default) { base.Run(ignored, canInlineContinuationTask); } else { RunOrScheduleAction(m_action, m_scheduler, m_capturedContext, canInlineContinuationTask); } } /// <summary>Inlines or schedules the action to run on the specified non-default scheduler.</summary> /// <param name="action">The action to invoke. Must not be null.</param> /// <param name="scheduler">The non-default scheduler with which to invoke the action. Must not be null and must not be the default scheduler.</param> /// <param name="capturedContext">The ExecutionContext with which to run the action, or null to not flow execution context.</param> /// <param name="allowInlining">true if inlining is permitted; otherwise, false.</param> internal static void RunOrScheduleAction(Action action, TaskScheduler scheduler, ExecutionContext? capturedContext, bool allowInlining) { Debug.Assert(action != null); Debug.Assert(scheduler != null && scheduler != TaskScheduler.Default); // We permit inlining if the caller allows us to, and // either we're on a thread pool thread (in which case we're fine running arbitrary code) // or we're already on the target scheduler (in which case we'll just ask the scheduler // whether it's ok to run here). We include the IsThreadPoolThread check here, whereas // we don't in AwaitTaskContinuation.Run, since here it expands what's allowed as opposed // to in AwaitTaskContinuation.Run where it restricts what's allowed. bool inlineIfPossible = allowInlining && (TaskScheduler.InternalCurrent == scheduler || Thread.CurrentThread.IsThreadPoolThread); // Create the continuation task task. If we're allowed to inline, try to do so. // The target scheduler may still deny us from executing on this thread, in which case this'll be queued. Task task = CreateTask(static state => { try { ((Action)state!)(); } catch (Exception exception) { Task.ThrowAsync(exception, targetContext: null); } }, action, scheduler, capturedContext); if (inlineIfPossible) { InlineIfPossibleOrElseQueue(task, needsProtection: false); } else { // We need to run asynchronously, so just schedule the task. try { task.ScheduleAndStart(needsProtection: false); } catch (TaskSchedulerException) { } // No further action is necessary, as ScheduleAndStart already transitioned task to faulted } } } /// <summary>Base task continuation class used for await continuations.</summary> internal class AwaitTaskContinuation : TaskContinuation, IThreadPoolWorkItem { /// <summary>The ExecutionContext with which to run the continuation.</summary> protected readonly ExecutionContext? m_capturedContext; /// <summary>The action to invoke.</summary> protected readonly Action m_action; protected int m_continuationId; /// <summary>Initializes the continuation.</summary> /// <param name="action">The action to invoke. Must not be null.</param> /// <param name="flowExecutionContext">Whether to capture and restore ExecutionContext.</param> internal AwaitTaskContinuation(Action action, bool flowExecutionContext) { Debug.Assert(action != null); m_action = action; if (flowExecutionContext) { m_capturedContext = ExecutionContext.Capture(); } } /// <summary>Creates a task to run the action with the specified state on the specified scheduler.</summary> /// <param name="action">The action to run. Must not be null.</param> /// <param name="state">The state to pass to the action. Must not be null.</param> /// <param name="scheduler">The scheduler to target.</param> /// <param name="capturedContext">The ExecutionContext with which to run the action, or null to not flow execution context.</param> /// <returns>The created task.</returns> protected static Task CreateTask(Action<object?> action, object? state, TaskScheduler scheduler, ExecutionContext? capturedContext) { Debug.Assert(action != null); Debug.Assert(scheduler != null); return new Task( action, state, null, default, TaskCreationOptions.None, InternalTaskOptions.QueuedByRuntime, scheduler) { CapturedContext = capturedContext }; } /// <summary>Inlines or schedules the continuation onto the default scheduler.</summary> /// <param name="task">The antecedent task, which is ignored.</param> /// <param name="canInlineContinuationTask">true if inlining is permitted; otherwise, false.</param> internal override void Run(Task task, bool canInlineContinuationTask) { // For the base AwaitTaskContinuation, we allow inlining if our caller allows it // and if we're in a "valid location" for it. See the comments on // IsValidLocationForInlining for more about what's valid. For performance // reasons we would like to always inline, but we don't in some cases to avoid // running arbitrary amounts of work in suspected "bad locations", like UI threads. if (canInlineContinuationTask && IsValidLocationForInlining) { RunCallback(GetInvokeActionCallback(), m_action, ref Task.t_currentTask); // any exceptions from m_action will be handled by s_callbackRunAction } else { TplEventSource log = TplEventSource.Log; if (log.IsEnabled()) { m_continuationId = Task.NewId(); log.AwaitTaskContinuationScheduled((task.ExecutingTaskScheduler ?? TaskScheduler.Default).Id, task.Id, m_continuationId); } // We couldn't inline, so now we need to schedule it ThreadPool.UnsafeQueueUserWorkItemInternal(this, preferLocal: true); } } /// <summary> /// Gets whether the current thread is an appropriate location to inline a continuation's execution. /// </summary> /// <remarks> /// Returns whether SynchronizationContext is null and we're in the default scheduler. /// If the await had a SynchronizationContext/TaskScheduler where it began and the /// default/ConfigureAwait(true) was used, then we won't be on this path. If, however, /// ConfigureAwait(false) was used, or the SynchronizationContext and TaskScheduler were /// naturally null/Default, then we might end up here. If we do, we need to make sure /// that we don't execute continuations in a place that isn't set up to handle them, e.g. /// running arbitrary amounts of code on the UI thread. It would be "correct", but very /// expensive, to always run the continuations asynchronously, incurring lots of context /// switches and allocations and locks and the like. As such, we employ the heuristic /// that if the current thread has a non-null SynchronizationContext or a non-default /// scheduler, then we better not run arbitrary continuations here. /// </remarks> internal static bool IsValidLocationForInlining { get { // If there's a SynchronizationContext, we'll be conservative and say // this is a bad location to inline. SynchronizationContext? ctx = SynchronizationContext.Current; if (ctx != null && ctx.GetType() != typeof(SynchronizationContext)) return false; // Similarly, if there's a non-default TaskScheduler, we'll be conservative // and say this is a bad location to inline. TaskScheduler? sched = TaskScheduler.InternalCurrent; return sched == null || sched == TaskScheduler.Default; } } void IThreadPoolWorkItem.Execute() { TplEventSource log = TplEventSource.Log; ExecutionContext? context = m_capturedContext; if (!log.IsEnabled() && context == null) { m_action(); return; } Guid savedActivityId = default; if (log.IsEnabled() && log.TasksSetActivityIds && m_continuationId != 0) { Guid activityId = TplEventSource.CreateGuidForTaskID(m_continuationId); Diagnostics.Tracing.EventSource.SetCurrentThreadActivityId(activityId, out savedActivityId); } try { // We're not inside of a task, so t_currentTask doesn't need to be specially maintained. // We're on a thread pool thread with no higher-level callers, so exceptions can just propagate. ExecutionContext.CheckThreadPoolAndContextsAreDefault(); // If there's no execution context or Default, just invoke the delegate as ThreadPool is on Default context. // We don't have to use ExecutionContext.Run for the Default context here as there is no extra processing after the delegate if (context == null || context.IsDefault) { m_action(); } // If there is an execution context, get the cached delegate and run the action under the context. else { ExecutionContext.RunForThreadPoolUnsafe(context, s_invokeAction, m_action); } // ThreadPoolWorkQueue.Dispatch handles notifications and reset context back to default } finally { if (log.IsEnabled() && log.TasksSetActivityIds && m_continuationId != 0) { Diagnostics.Tracing.EventSource.SetCurrentThreadActivityId(savedActivityId); } } } /// <summary>Cached delegate that invokes an Action passed as an object parameter.</summary> private static readonly ContextCallback s_invokeContextCallback = static (state) => { Debug.Assert(state is Action); ((Action)state)(); }; private static readonly Action<Action> s_invokeAction = (action) => action(); [MethodImpl(MethodImplOptions.AggressiveInlining)] protected static ContextCallback GetInvokeActionCallback() => s_invokeContextCallback; /// <summary>Runs the callback synchronously with the provided state.</summary> /// <param name="callback">The callback to run.</param> /// <param name="state">The state to pass to the callback.</param> /// <param name="currentTask">A reference to Task.t_currentTask.</param> protected void RunCallback(ContextCallback callback, object? state, ref Task? currentTask) { Debug.Assert(callback != null); Debug.Assert(currentTask == Task.t_currentTask); // Pretend there's no current task, so that no task is seen as a parent // and TaskScheduler.Current does not reflect false information Task? prevCurrentTask = currentTask; try { if (prevCurrentTask != null) currentTask = null; 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 exception) // we explicitly do not request handling of dangerous exceptions like AVs { Task.ThrowAsync(exception, targetContext: null); } finally { // Restore the current task information if (prevCurrentTask != null) currentTask = prevCurrentTask; } } /// <summary>Invokes or schedules the action to be executed.</summary> /// <param name="action">The action to invoke or queue.</param> /// <param name="allowInlining"> /// true to allow inlining, or false to force the action to run asynchronously. /// </param> /// <remarks> /// No ExecutionContext work is performed used. This method is only used in the /// case where a raw Action continuation delegate was stored into the Task, which /// only happens in Task.SetContinuationForAwait if execution context flow was disabled /// via using TaskAwaiter.UnsafeOnCompleted or a similar path. /// </remarks> internal static void RunOrScheduleAction(Action action, bool allowInlining) { ref Task? currentTask = ref Task.t_currentTask; Task? prevCurrentTask = currentTask; // If we're not allowed to run here, schedule the action if (!allowInlining || !IsValidLocationForInlining) { UnsafeScheduleAction(action, prevCurrentTask); return; } // Otherwise, run it, making sure that t_currentTask is null'd out appropriately during the execution try { if (prevCurrentTask != null) currentTask = null; action(); } catch (Exception exception) { Task.ThrowAsync(exception, targetContext: null); } finally { if (prevCurrentTask != null) currentTask = prevCurrentTask; } } /// <summary>Invokes or schedules the action to be executed.</summary> /// <param name="box">The <see cref="IAsyncStateMachineBox"/> that needs to be invoked or queued.</param> /// <param name="allowInlining"> /// true to allow inlining, or false to force the box's action to run asynchronously. /// </param> internal static void RunOrScheduleAction(IAsyncStateMachineBox box, bool allowInlining) { // Same logic as in the RunOrScheduleAction(Action, ...) overload, except invoking // box.Invoke instead of action(). ref Task? currentTask = ref Task.t_currentTask; Task? prevCurrentTask = currentTask; // If we're not allowed to run here, schedule the action if (!allowInlining || !IsValidLocationForInlining) { if (AsyncInstrumentation.IsActive && AsyncInstrumentation.LoadFlags(out AsyncInstrumentation.Flags flags)) { if (AsyncInstrumentation.IsEnabled.AsyncProfiler(flags)) { box = AsyncStateMachineDispatcherInfo.CreateDispatcher(box, flags); } // If logging is disabled, we can simply queue the box itself as a custom work // item, and its work item execution will just invoke its MoveNext. However, if // logging is enabled, there is pre/post-work we need to do around logging to // match what's done for other continuations, and that requires flowing additional // information into the continuation, which we don't want to burden other cases of the // box with... so, in that case we just delegate to the AwaitTaskContinuation-based // path that already handles this, albeit at the expense of allocating the ATC // object, and potentially forcing the box's delegate into existence, when logging // is enabled. if (AsyncInstrumentation.IsEnabled.Tpl(flags)) { UnsafeScheduleAction(box.MoveNextAction, prevCurrentTask); return; } } ThreadPool.UnsafeQueueUserWorkItemInternal(box, preferLocal: true); return; } // Otherwise, run it, making sure that t_currentTask is null'd out appropriately during the execution try { if (prevCurrentTask != null) currentTask = null; box.MoveNext(); } catch (Exception exception) { Task.ThrowAsync(exception, targetContext: null); } finally { if (prevCurrentTask != null) currentTask = prevCurrentTask; } } /// <summary>Schedules the action to be executed. No ExecutionContext work is performed used.</summary> /// <param name="action">The action to invoke or queue.</param> /// <param name="task">The task scheduling the action.</param> internal static void UnsafeScheduleAction(Action action, Task? task) { AwaitTaskContinuation atc = new AwaitTaskContinuation(action, flowExecutionContext: false); TplEventSource log = TplEventSource.Log; if (log.IsEnabled() && task != null) { atc.m_continuationId = Task.NewId(); log.AwaitTaskContinuationScheduled((task.ExecutingTaskScheduler ?? TaskScheduler.Default).Id, task.Id, atc.m_continuationId); } ThreadPool.UnsafeQueueUserWorkItemInternal(atc, preferLocal: true); } internal override Delegate[] GetDelegateContinuationsForDebugger() { Debug.Assert(m_action != null); return [AsyncMethodBuilderCore.TryGetStateMachineForDebugger(m_action)]; } } }