diff options
Diffstat (limited to 'src/mscorlib/src/System/Threading/Tasks/TaskContinuation.cs')
-rw-r--r-- | src/mscorlib/src/System/Threading/Tasks/TaskContinuation.cs | 867 |
1 files changed, 867 insertions, 0 deletions
diff --git a/src/mscorlib/src/System/Threading/Tasks/TaskContinuation.cs b/src/mscorlib/src/System/Threading/Tasks/TaskContinuation.cs new file mode 100644 index 0000000000..4c035dfddb --- /dev/null +++ b/src/mscorlib/src/System/Threading/Tasks/TaskContinuation.cs @@ -0,0 +1,867 @@ +// 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. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// +// +// Implementation of task continuations, TaskContinuation, and its descendants. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System.Security; +using System.Diagnostics.Contracts; +using System.Runtime.ExceptionServices; +using System.Runtime.CompilerServices; +using System.Threading; + +#if FEATURE_COMINTEROP +using System.Runtime.InteropServices.WindowsRuntime; +#endif // FEATURE_COMINTEROP + +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, ref StackCrawlMark stackMark) : + base(action, state, Task.InternalCurrentIfAttached(creationOptions), default(CancellationToken), creationOptions, internalOptions, null) + { + Contract.Requires(action is Action<Task> || action is Action<Task, object>, + "Invalid delegate type in ContinuationTaskFromTask"); + m_antecedent = antecedent; + PossiblyCaptureContext(ref stackMark); + } + + /// <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. + var antecedent = m_antecedent; + Contract.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 + Contract.Assert(m_action != null); + var action = m_action as Action<Task>; + if (action != null) + { + action(antecedent); + return; + } + var actionWithState = m_action as Action<Task, object>; + if (actionWithState != null) + { + actionWithState(antecedent, m_stateObject); + return; + } + Contract.Assert(false, "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, ref StackCrawlMark stackMark) : + base(function, state, Task.InternalCurrentIfAttached(creationOptions), default(CancellationToken), creationOptions, internalOptions, null) + { + Contract.Requires(function is Func<Task, TResult> || function is Func<Task, object, TResult>, + "Invalid delegate type in ContinuationResultTaskFromTask"); + m_antecedent = antecedent; + PossiblyCaptureContext(ref stackMark); + } + + /// <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. + var antecedent = m_antecedent; + Contract.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 + Contract.Assert(m_action != null); + var func = m_action as Func<Task, TResult>; + if (func != null) + { + m_result = func(antecedent); + return; + } + var funcWithState = m_action as Func<Task, object, TResult>; + if (funcWithState != null) + { + m_result = funcWithState(antecedent, m_stateObject); + return; + } + Contract.Assert(false, "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, ref StackCrawlMark stackMark) : + base(action, state, Task.InternalCurrentIfAttached(creationOptions), default(CancellationToken), creationOptions, internalOptions, null) + { + Contract.Requires(action is Action<Task<TAntecedentResult>> || action is Action<Task<TAntecedentResult>, object>, + "Invalid delegate type in ContinuationTaskFromResultTask"); + m_antecedent = antecedent; + PossiblyCaptureContext(ref stackMark); + } + + /// <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. + var antecedent = m_antecedent; + Contract.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 + Contract.Assert(m_action != null); + var action = m_action as Action<Task<TAntecedentResult>>; + if (action != null) + { + action(antecedent); + return; + } + var actionWithState = m_action as Action<Task<TAntecedentResult>, object>; + if (actionWithState != null) + { + actionWithState(antecedent, m_stateObject); + return; + } + Contract.Assert(false, "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, ref StackCrawlMark stackMark) : + base(function, state, Task.InternalCurrentIfAttached(creationOptions), default(CancellationToken), creationOptions, internalOptions, null) + { + Contract.Requires(function is Func<Task<TAntecedentResult>, TResult> || function is Func<Task<TAntecedentResult>, object, TResult>, + "Invalid delegate type in ContinuationResultTaskFromResultTask"); + m_antecedent = antecedent; + PossiblyCaptureContext(ref stackMark); + } + + /// <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. + var antecedent = m_antecedent; + Contract.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 + Contract.Assert(m_action != null); + var func = m_action as Func<Task<TAntecedentResult>, TResult>; + if (func != null) + { + m_result = func(antecedent); + return; + } + var funcWithState = m_action as Func<Task<TAntecedentResult>, object, TResult>; + if (funcWithState != null) + { + m_result = funcWithState(antecedent, m_stateObject); + return; + } + Contract.Assert(false, "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 bCanInlineContinuationTask); + + /// <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> + [SecuritySafeCritical] + protected static void InlineIfPossibleOrElseQueue(Task task, bool needsProtection) + { + Contract.Requires(task != null); + Contract.Assert(task.m_taskScheduler != null); + + // Set the TASK_STATE_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 |= Task.TASK_STATE_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() + if (!(e is ThreadAbortException && + (task.m_stateFlags & Task.TASK_STATE_THREAD_WAS_ABORTED) != 0)) // this ensures TAEs from QueueTask will be wrapped in TSE + { + TaskSchedulerException tse = new TaskSchedulerException(e); + task.AddException(tse); + task.Finish(false); + } + + // Don't re-throw. + } + } + + internal abstract Delegate[] GetDelegateContinuationsForDebugger(); + + } + + /// <summary>Provides the standard implementation of a task continuation.</summary> + internal class StandardTaskContinuation : TaskContinuation + { + /// <summary>The unstarted continuation task.</summary> + internal readonly 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 StandardTaskContinuation(Task task, TaskContinuationOptions options, TaskScheduler scheduler) + { + Contract.Requires(task != null, "TaskContinuation ctor: task is null"); + Contract.Requires(scheduler != null, "TaskContinuation ctor: scheduler is null"); + m_task = task; + m_options = options; + m_taskScheduler = scheduler; + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, m_task.Id, "Task.ContinueWith: " + ((Delegate)task.m_action).Method.Name, 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="bCanInlineContinuationTask">Whether the continuation can be inlined.</param> + internal override void Run(Task completedTask, bool bCanInlineContinuationTask) + { + Contract.Assert(completedTask != null); + Contract.Assert(completedTask.IsCompleted, "ContinuationTask.Run(): completedTask not completed"); + + // Check if the completion status of the task works with the desired + // activation criteria of the TaskContinuationOptions. + TaskContinuationOptions options = m_options; + bool isRightKind = + completedTask.IsRanToCompletion ? + (options & TaskContinuationOptions.NotOnRanToCompletion) == 0 : + (completedTask.IsCanceled ? + (options & TaskContinuationOptions.NotOnCanceled) == 0 : + (options & TaskContinuationOptions.NotOnFaulted) == 0); + + // If the completion status is allowed, run the continuation. + Task continuationTask = m_task; + 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 (!continuationTask.IsCanceled && AsyncCausalityTracer.LoggingOn) + { + // Log now that we are sure that this continuation is being ran + AsyncCausalityTracer.TraceOperationRelation(CausalityTraceLevel.Important, 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 (bCanInlineContinuationTask && // 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. + } + } + } + // Otherwise, the final state of this task does not match the desired + // continuation activation criteria; cancel it to denote this. + else continuationTask.InternalCancel(false); + } + + internal override Delegate[] GetDelegateContinuationsForDebugger() + { + if (m_task.m_action == null) + { + return m_task.GetDelegateContinuationsForDebugger(); + } + + return new Delegate[] { m_task.m_action as Delegate }; + } + } + + /// <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 readonly static SendOrPostCallback s_postCallback = state => ((Action)state)(); // can't use InvokeAction as it's SecurityCritical + /// <summary>Cached delegate for PostAction</summary> + [SecurityCritical] + 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> + /// <param name="stackMark">The captured stack mark.</param> + [SecurityCritical] + internal SynchronizationContextAwaitTaskContinuation( + SynchronizationContext context, Action action, bool flowExecutionContext, ref StackCrawlMark stackMark) : + base(action, flowExecutionContext, ref stackMark) + { + Contract.Assert(context != null); + m_syncContext = context; + } + + /// <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> + [SecuritySafeCritical] + 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.CurrentNoFlow) + { + RunCallback(GetInvokeActionCallback(), m_action, ref Task.t_currentTask); + } + // Otherwise, Post the action back to the SynchronizationContext. + else + { + TplEtwProvider etwLog = TplEtwProvider.Log; + if (etwLog.IsEnabled()) + { + m_continuationId = Task.NewId(); + etwLog.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> + [SecurityCritical] + private static void PostAction(object state) + { + var c = (SynchronizationContextAwaitTaskContinuation)state; + + TplEtwProvider etwLog = TplEtwProvider.Log; + if (etwLog.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 savedActivityId; + Guid activityId = TplEtwProvider.CreateGuidForTaskID(continuationId); + System.Diagnostics.Tracing.EventSource.SetCurrentThreadActivityId(activityId, out savedActivityId); + try { action(); } + finally { System.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)] + [SecurityCritical] + private static ContextCallback GetPostActionCallback() + { + ContextCallback callback = s_postActionCallback; + if (callback == null) { s_postActionCallback = callback = PostAction; } // lazily initialize SecurityCritical delegate + return callback; + } + } + + /// <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> + /// <param name="stackMark">The captured stack mark.</param> + [SecurityCritical] + internal TaskSchedulerAwaitTaskContinuation( + TaskScheduler scheduler, Action action, bool flowExecutionContext, ref StackCrawlMark stackMark) : + base(action, flowExecutionContext, ref stackMark) + { + Contract.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 + { + // 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 = canInlineContinuationTask && + (TaskScheduler.InternalCurrent == m_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. + var task = CreateTask(state => { + try { ((Action)state)(); } + catch (Exception exc) { ThrowAsyncIfNecessary(exc); } + }, m_action, m_scheduler); + + 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> + private 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> + /// <param name="stackMark">The captured stack mark with which to construct an ExecutionContext.</param> + [SecurityCritical] + internal AwaitTaskContinuation(Action action, bool flowExecutionContext, ref StackCrawlMark stackMark) + { + Contract.Requires(action != null); + m_action = action; + if (flowExecutionContext) + { + m_capturedContext = ExecutionContext.Capture( + ref stackMark, + ExecutionContext.CaptureOptions.IgnoreSyncCtx | ExecutionContext.CaptureOptions.OptimizeDefaultCase); + } + } + + /// <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> + [SecurityCritical] + internal AwaitTaskContinuation(Action action, bool flowExecutionContext) + { + Contract.Requires(action != null); + m_action = action; + if (flowExecutionContext) + { + m_capturedContext = ExecutionContext.FastCapture(); + } + } + + /// <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> + /// <returns>The created task.</returns> + protected Task CreateTask(Action<object> action, object state, TaskScheduler scheduler) + { + Contract.Requires(action != null); + Contract.Requires(scheduler != null); + + return new Task( + action, state, null, default(CancellationToken), + TaskCreationOptions.None, InternalTaskOptions.QueuedByRuntime, scheduler) + { + CapturedContext = m_capturedContext + }; + } + + /// <summary>Inlines or schedules the continuation onto the default scheduler.</summary> + /// <param name="ignored">The antecedent task, which is ignored.</param> + /// <param name="canInlineContinuationTask">true if inlining is permitted; otherwise, false.</param> + [SecuritySafeCritical] + 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 + { + TplEtwProvider etwLog = TplEtwProvider.Log; + if (etwLog.IsEnabled()) + { + m_continuationId = Task.NewId(); + etwLog.AwaitTaskContinuationScheduled((task.ExecutingTaskScheduler ?? TaskScheduler.Default).Id, task.Id, m_continuationId); + } + + // We couldn't inline, so now we need to schedule it + ThreadPool.UnsafeQueueCustomWorkItem(this, forceGlobal: false); + } + } + + /// <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. + var ctx = SynchronizationContext.CurrentNoFlow; + 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. + var sched = TaskScheduler.InternalCurrent; + return sched == null || sched == TaskScheduler.Default; + } + } + + /// <summary>IThreadPoolWorkItem override, which is the entry function for this when the ThreadPool scheduler decides to run it.</summary> + [SecurityCritical] + void ExecuteWorkItemHelper() + { + var etwLog = TplEtwProvider.Log; + Guid savedActivityId = Guid.Empty; + if (etwLog.TasksSetActivityIds && m_continuationId != 0) + { + Guid activityId = TplEtwProvider.CreateGuidForTaskID(m_continuationId); + System.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. + + // If there's no execution context, just invoke the delegate. + if (m_capturedContext == null) + { + m_action(); + } + // If there is an execution context, get the cached delegate and run the action under the context. + else + { + try + { + ExecutionContext.Run(m_capturedContext, GetInvokeActionCallback(), m_action, true); + } + finally { m_capturedContext.Dispose(); } + } + } + finally + { + if (etwLog.TasksSetActivityIds && m_continuationId != 0) + { + System.Diagnostics.Tracing.EventSource.SetCurrentThreadActivityId(savedActivityId); + } + } + } + + [SecurityCritical] + void IThreadPoolWorkItem.ExecuteWorkItem() + { + // inline the fast path + if (m_capturedContext == null && !TplEtwProvider.Log.IsEnabled() + ) + { + m_action(); + } + else + { + ExecuteWorkItemHelper(); + } + } + + /// <summary> + /// The ThreadPool calls this if a ThreadAbortException is thrown while trying to execute this workitem. + /// </summary> + [SecurityCritical] + void IThreadPoolWorkItem.MarkAborted(ThreadAbortException tae) { /* nop */ } + + /// <summary>Cached delegate that invokes an Action passed as an object parameter.</summary> + [SecurityCritical] + private static ContextCallback s_invokeActionCallback; + + /// <summary>Runs an action provided as an object parameter.</summary> + /// <param name="state">The Action to invoke.</param> + [SecurityCritical] + private static void InvokeAction(object state) { ((Action)state)(); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [SecurityCritical] + protected static ContextCallback GetInvokeActionCallback() + { + ContextCallback callback = s_invokeActionCallback; + if (callback == null) { s_invokeActionCallback = callback = InvokeAction; } // lazily initialize SecurityCritical delegate + return callback; + } + + /// <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> + [SecurityCritical] + protected void RunCallback(ContextCallback callback, object state, ref Task currentTask) + { + Contract.Requires(callback != null); + Contract.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 + var prevCurrentTask = currentTask; + try + { + if (prevCurrentTask != null) currentTask = null; + + // If there's no captured context, just run the callback directly. + if (m_capturedContext == null) callback(state); + // Otherwise, use the captured context to do so. + else ExecutionContext.Run(m_capturedContext, callback, state, true); + } + catch (Exception exc) // we explicitly do not request handling of dangerous exceptions like AVs + { + ThrowAsyncIfNecessary(exc); + } + finally + { + // Restore the current task information + if (prevCurrentTask != null) currentTask = prevCurrentTask; + + // Clean up after the execution context, which is only usable once. + if (m_capturedContext != null) m_capturedContext.Dispose(); + } + } + + /// <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> + /// <param name="currentTask"> + /// A reference to the t_currentTask thread static value. + /// This is passed by-ref rather than accessed in the method in order to avoid + /// unnecessary thread-static writes. + /// </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> + [SecurityCritical] + internal static void RunOrScheduleAction(Action action, bool allowInlining, ref Task currentTask) + { + Contract.Assert(currentTask == Task.t_currentTask); + + // If we're not allowed to run here, schedule the action + if (!allowInlining || !IsValidLocationForInlining) + { + UnsafeScheduleAction(action, currentTask); + return; + } + + // Otherwise, run it, making sure that t_currentTask is null'd out appropriately during the execution + Task prevCurrentTask = currentTask; + try + { + if (prevCurrentTask != null) currentTask = null; + action(); + } + catch (Exception exception) + { + ThrowAsyncIfNecessary(exception); + } + 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> + [SecurityCritical] + internal static void UnsafeScheduleAction(Action action, Task task) + { + AwaitTaskContinuation atc = new AwaitTaskContinuation(action, flowExecutionContext: false); + + var etwLog = TplEtwProvider.Log; + if (etwLog.IsEnabled() && task != null) + { + atc.m_continuationId = Task.NewId(); + etwLog.AwaitTaskContinuationScheduled((task.ExecutingTaskScheduler ?? TaskScheduler.Default).Id, task.Id, atc.m_continuationId); + } + + ThreadPool.UnsafeQueueCustomWorkItem(atc, forceGlobal: false); + } + + /// <summary>Throws the exception asynchronously on the ThreadPool.</summary> + /// <param name="exc">The exception to throw.</param> + protected static void ThrowAsyncIfNecessary(Exception exc) + { + // Awaits should never experience an exception (other than an TAE or ADUE), + // unless a malicious user is explicitly passing a throwing action into the TaskAwaiter. + // We don't want to allow the exception to propagate on this stack, as it'll emerge in random places, + // and we can't fail fast, as that would allow for elevation of privilege. + // + // If unhandled error reporting APIs are available use those, otherwise since this + // would have executed on the thread pool otherwise, let it propagate there. + + if (!(exc is ThreadAbortException || exc is AppDomainUnloadedException)) + { +#if FEATURE_COMINTEROP + if (!WindowsRuntimeMarshal.ReportUnhandledError(exc)) +#endif // FEATURE_COMINTEROP + { + var edi = ExceptionDispatchInfo.Capture(exc); + ThreadPool.QueueUserWorkItem(s => ((ExceptionDispatchInfo)s).Throw(), edi); + } + } + } + + internal override Delegate[] GetDelegateContinuationsForDebugger() + { + Contract.Assert(m_action != null); + return new Delegate[] { AsyncMethodBuilderCore.TryGetStateMachineForDebugger(m_action) }; + } + } + +} |