3 // Copyright (c) Microsoft Corporation. All rights reserved.
6 // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
10 // <OWNER>[....]</OWNER>
12 // Implementation of task continuations, TaskContinuation, and its descendants.
14 // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
16 using System.Security;
17 using System.Diagnostics.Contracts;
18 using System.Runtime.ExceptionServices;
19 using System.Runtime.CompilerServices;
20 using System.Threading;
22 #if FEATURE_COMINTEROP
23 using System.Runtime.InteropServices.WindowsRuntime;
24 #endif // FEATURE_COMINTEROP
26 namespace System.Threading.Tasks
28 // Task type used to implement: Task ContinueWith(Action<Task,...>)
29 internal sealed class ContinuationTaskFromTask : Task
31 private Task m_antecedent;
33 public ContinuationTaskFromTask(
34 Task antecedent, Delegate action, object state, TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, ref StackCrawlMark stackMark) :
35 base(action, state, Task.InternalCurrentIfAttached(creationOptions), default(CancellationToken), creationOptions, internalOptions, null)
37 Contract.Requires(action is Action<Task> || action is Action<Task, object>,
38 "Invalid delegate type in ContinuationTaskFromTask");
39 m_antecedent = antecedent;
40 PossiblyCaptureContext(ref stackMark);
44 /// Evaluates the value selector of the Task which is passed in as an object and stores the result.
46 internal override void InnerInvoke()
48 // Get and null out the antecedent. This is crucial to avoid a memory
49 // leak with long chains of continuations.
50 var antecedent = m_antecedent;
51 Contract.Assert(antecedent != null,
52 "No antecedent was set for the ContinuationTaskFromTask.");
55 // Notify the debugger we're completing an asynchronous wait on a task
56 antecedent.NotifyDebuggerOfWaitCompletionIfNecessary();
58 // Invoke the delegate
59 Contract.Assert(m_action != null);
60 var action = m_action as Action<Task>;
66 var actionWithState = m_action as Action<Task, object>;
67 if (actionWithState != null)
69 actionWithState(antecedent, m_stateObject);
72 Contract.Assert(false, "Invalid m_action in ContinuationTaskFromTask");
76 // Task type used to implement: Task<TResult> ContinueWith(Func<Task,...>)
77 internal sealed class ContinuationResultTaskFromTask<TResult> : Task<TResult>
79 private Task m_antecedent;
81 public ContinuationResultTaskFromTask(
82 Task antecedent, Delegate function, object state, TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, ref StackCrawlMark stackMark) :
83 base(function, state, Task.InternalCurrentIfAttached(creationOptions), default(CancellationToken), creationOptions, internalOptions, null)
85 Contract.Requires(function is Func<Task, TResult> || function is Func<Task, object, TResult>,
86 "Invalid delegate type in ContinuationResultTaskFromTask");
87 m_antecedent = antecedent;
88 PossiblyCaptureContext(ref stackMark);
92 /// Evaluates the value selector of the Task which is passed in as an object and stores the result.
94 internal override void InnerInvoke()
96 // Get and null out the antecedent. This is crucial to avoid a memory
97 // leak with long chains of continuations.
98 var antecedent = m_antecedent;
99 Contract.Assert(antecedent != null,
100 "No antecedent was set for the ContinuationResultTaskFromTask.");
103 // Notify the debugger we're completing an asynchronous wait on a task
104 antecedent.NotifyDebuggerOfWaitCompletionIfNecessary();
106 // Invoke the delegate
107 Contract.Assert(m_action != null);
108 var func = m_action as Func<Task, TResult>;
111 m_result = func(antecedent);
114 var funcWithState = m_action as Func<Task, object, TResult>;
115 if (funcWithState != null)
117 m_result = funcWithState(antecedent, m_stateObject);
120 Contract.Assert(false, "Invalid m_action in ContinuationResultTaskFromTask");
124 // Task type used to implement: Task ContinueWith(Action<Task<TAntecedentResult>,...>)
125 internal sealed class ContinuationTaskFromResultTask<TAntecedentResult> : Task
127 private Task<TAntecedentResult> m_antecedent;
129 public ContinuationTaskFromResultTask(
130 Task<TAntecedentResult> antecedent, Delegate action, object state, TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, ref StackCrawlMark stackMark) :
131 base(action, state, Task.InternalCurrentIfAttached(creationOptions), default(CancellationToken), creationOptions, internalOptions, null)
133 Contract.Requires(action is Action<Task<TAntecedentResult>> || action is Action<Task<TAntecedentResult>, object>,
134 "Invalid delegate type in ContinuationTaskFromResultTask");
135 m_antecedent = antecedent;
136 PossiblyCaptureContext(ref stackMark);
140 /// Evaluates the value selector of the Task which is passed in as an object and stores the result.
142 internal override void InnerInvoke()
144 // Get and null out the antecedent. This is crucial to avoid a memory
145 // leak with long chains of continuations.
146 var antecedent = m_antecedent;
147 Contract.Assert(antecedent != null,
148 "No antecedent was set for the ContinuationTaskFromResultTask.");
151 // Notify the debugger we're completing an asynchronous wait on a task
152 antecedent.NotifyDebuggerOfWaitCompletionIfNecessary();
154 // Invoke the delegate
155 Contract.Assert(m_action != null);
156 var action = m_action as Action<Task<TAntecedentResult>>;
162 var actionWithState = m_action as Action<Task<TAntecedentResult>, object>;
163 if (actionWithState != null)
165 actionWithState(antecedent, m_stateObject);
168 Contract.Assert(false, "Invalid m_action in ContinuationTaskFromResultTask");
172 // Task type used to implement: Task<TResult> ContinueWith(Func<Task<TAntecedentResult>,...>)
173 internal sealed class ContinuationResultTaskFromResultTask<TAntecedentResult, TResult> : Task<TResult>
175 private Task<TAntecedentResult> m_antecedent;
177 public ContinuationResultTaskFromResultTask(
178 Task<TAntecedentResult> antecedent, Delegate function, object state, TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, ref StackCrawlMark stackMark) :
179 base(function, state, Task.InternalCurrentIfAttached(creationOptions), default(CancellationToken), creationOptions, internalOptions, null)
181 Contract.Requires(function is Func<Task<TAntecedentResult>, TResult> || function is Func<Task<TAntecedentResult>, object, TResult>,
182 "Invalid delegate type in ContinuationResultTaskFromResultTask");
183 m_antecedent = antecedent;
184 PossiblyCaptureContext(ref stackMark);
188 /// Evaluates the value selector of the Task which is passed in as an object and stores the result.
190 internal override void InnerInvoke()
192 // Get and null out the antecedent. This is crucial to avoid a memory
193 // leak with long chains of continuations.
194 var antecedent = m_antecedent;
195 Contract.Assert(antecedent != null,
196 "No antecedent was set for the ContinuationResultTaskFromResultTask.");
199 // Notify the debugger we're completing an asynchronous wait on a task
200 antecedent.NotifyDebuggerOfWaitCompletionIfNecessary();
202 // Invoke the delegate
203 Contract.Assert(m_action != null);
204 var func = m_action as Func<Task<TAntecedentResult>, TResult>;
207 m_result = func(antecedent);
210 var funcWithState = m_action as Func<Task<TAntecedentResult>, object, TResult>;
211 if (funcWithState != null)
213 m_result = funcWithState(antecedent, m_stateObject);
216 Contract.Assert(false, "Invalid m_action in ContinuationResultTaskFromResultTask");
220 // For performance reasons, we don't just have a single way of representing
221 // a continuation object. Rather, we have a hierarchy of types:
222 // - TaskContinuation: abstract base that provides a virtual Run method
223 // - StandardTaskContinuation: wraps a task,options,and scheduler, and overrides Run to process the task with that configuration
224 // - AwaitTaskContinuation: base for continuations created through TaskAwaiter; targets default scheduler by default
225 // - TaskSchedulerAwaitTaskContinuation: awaiting with a non-default TaskScheduler
226 // - SynchronizationContextAwaitTaskContinuation: awaiting with a "current" [....] ctx
228 /// <summary>Represents a continuation.</summary>
229 internal abstract class TaskContinuation
231 /// <summary>Inlines or schedules the continuation.</summary>
232 /// <param name="completedTask">The antecedent task that has completed.</param>
233 /// <param name="canInlineContinuationTask">true if inlining is permitted; otherwise, false.</param>
234 internal abstract void Run(Task completedTask, bool bCanInlineContinuationTask);
236 /// <summary>Tries to run the task on the current thread, if possible; otherwise, schedules it.</summary>
237 /// <param name="task">The task to run</param>
238 /// <param name="needsProtection">
239 /// true if we need to protect against multiple threads racing to start/cancel the task; otherwise, false.
241 [SecuritySafeCritical]
242 protected static void InlineIfPossibleOrElseQueue(Task task, bool needsProtection)
244 Contract.Requires(task != null);
245 Contract.Assert(task.m_taskScheduler != null);
247 // Set the TASK_STATE_STARTED flag. This only needs to be done
248 // if the task may be canceled or if someone else has a reference to it
249 // that may try to execute it.
252 if (!task.MarkStarted())
253 return; // task has been previously started or canceled. Stop processing.
257 task.m_stateFlags |= Task.TASK_STATE_STARTED;
260 // Try to inline it but queue if we can't
263 if (!task.m_taskScheduler.TryRunInline(task, taskWasPreviouslyQueued: false))
265 task.m_taskScheduler.InternalQueueTask(task);
270 // Either TryRunInline() or QueueTask() threw an exception. Record the exception, marking the task as Faulted.
271 // However if it was a ThreadAbortException coming from TryRunInline we need to skip here,
272 // because it would already have been handled in Task.Execute()
273 if (!(e is ThreadAbortException &&
274 (task.m_stateFlags & Task.TASK_STATE_THREAD_WAS_ABORTED) != 0)) // this ensures TAEs from QueueTask will be wrapped in TSE
276 TaskSchedulerException tse = new TaskSchedulerException(e);
277 task.AddException(tse);
285 internal abstract Delegate[] GetDelegateContinuationsForDebugger();
289 /// <summary>Provides the standard implementation of a task continuation.</summary>
290 internal class StandardTaskContinuation : TaskContinuation
292 /// <summary>The unstarted continuation task.</summary>
293 internal readonly Task m_task;
294 /// <summary>The options to use with the continuation task.</summary>
295 internal readonly TaskContinuationOptions m_options;
296 /// <summary>The task scheduler with which to run the continuation task.</summary>
297 private readonly TaskScheduler m_taskScheduler;
299 /// <summary>Initializes a new continuation.</summary>
300 /// <param name="task">The task to be activated.</param>
301 /// <param name="options">The continuation options.</param>
302 /// <param name="scheduler">The scheduler to use for the continuation.</param>
303 internal StandardTaskContinuation(Task task, TaskContinuationOptions options, TaskScheduler scheduler)
305 Contract.Requires(task != null, "TaskContinuation ctor: task is null");
306 Contract.Requires(scheduler != null, "TaskContinuation ctor: scheduler is null");
309 m_taskScheduler = scheduler;
310 if (AsyncCausalityTracer.LoggingOn)
311 AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, m_task.Id, "Task.ContinueWith: " + ((Delegate)task.m_action).Method.Name, 0);
313 if (Task.s_asyncDebuggingEnabled)
315 Task.AddToActiveTasks(m_task);
319 /// <summary>Invokes the continuation for the target completion task.</summary>
320 /// <param name="completedTask">The completed task.</param>
321 /// <param name="bCanInlineContinuationTask">Whether the continuation can be inlined.</param>
322 internal override void Run(Task completedTask, bool bCanInlineContinuationTask)
324 Contract.Assert(completedTask != null);
325 Contract.Assert(completedTask.IsCompleted, "ContinuationTask.Run(): completedTask not completed");
327 // Check if the completion status of the task works with the desired
328 // activation criteria of the TaskContinuationOptions.
329 TaskContinuationOptions options = m_options;
331 completedTask.IsRanToCompletion ?
332 (options & TaskContinuationOptions.NotOnRanToCompletion) == 0 :
333 (completedTask.IsCanceled ?
334 (options & TaskContinuationOptions.NotOnCanceled) == 0 :
335 (options & TaskContinuationOptions.NotOnFaulted) == 0);
337 // If the completion status is allowed, run the continuation.
338 Task continuationTask = m_task;
341 //If the task was cancel before running (e.g a ContinueWhenAll with a cancelled caancelation token)
342 //we will still flow it to ScheduleAndStart() were it will check the status before running
343 //We check here to avoid faulty logs that contain a join event to an operation that was already set as completed.
344 if (!continuationTask.IsCanceled && AsyncCausalityTracer.LoggingOn)
346 // Log now that we are sure that this continuation is being ran
347 AsyncCausalityTracer.TraceOperationRelation(CausalityTraceLevel.Important, continuationTask.Id, CausalityRelation.AssignDelegate);
349 continuationTask.m_taskScheduler = m_taskScheduler;
351 // Either run directly or just queue it up for execution, depending
352 // on whether synchronous or asynchronous execution is wanted.
353 if (bCanInlineContinuationTask && // inlining is allowed by the caller
354 (options & TaskContinuationOptions.ExecuteSynchronously) != 0) // synchronous execution was requested by the continuation's creator
356 InlineIfPossibleOrElseQueue(continuationTask, needsProtection: true);
360 try { continuationTask.ScheduleAndStart(needsProtection: true); }
361 catch (TaskSchedulerException)
363 // No further action is necessary -- ScheduleAndStart() already transitioned the
364 // task to faulted. But we want to make sure that no exception is thrown from here.
368 // Otherwise, the final state of this task does not match the desired
369 // continuation activation criteria; cancel it to denote this.
370 else continuationTask.InternalCancel(false);
373 internal override Delegate[] GetDelegateContinuationsForDebugger()
375 if (m_task.m_action == null)
377 return m_task.GetDelegateContinuationsForDebugger();
380 return new Delegate[] { m_task.m_action as Delegate };
384 /// <summary>Task continuation for awaiting with a current synchronization context.</summary>
385 internal sealed class SynchronizationContextAwaitTaskContinuation : AwaitTaskContinuation
387 /// <summary>SendOrPostCallback delegate to invoke the action.</summary>
388 private readonly static SendOrPostCallback s_postCallback = state => ((Action)state)(); // can't use InvokeAction as it's SecurityCritical
389 /// <summary>Cached delegate for PostAction</summary>
391 private static ContextCallback s_postActionCallback;
392 /// <summary>The context with which to run the action.</summary>
393 private readonly SynchronizationContext m_syncContext;
395 /// <summary>Initializes the SynchronizationContextAwaitTaskContinuation.</summary>
396 /// <param name="context">The synchronization context with which to invoke the action. Must not be null.</param>
397 /// <param name="action">The action to invoke. Must not be null.</param>
398 /// <param name="flowExecutionContext">Whether to capture and restore ExecutionContext.</param>
399 /// <param name="stackMark">The captured stack mark.</param>
401 internal SynchronizationContextAwaitTaskContinuation(
402 SynchronizationContext context, Action action, bool flowExecutionContext, ref StackCrawlMark stackMark) :
403 base(action, flowExecutionContext, ref stackMark)
405 Contract.Assert(context != null);
406 m_syncContext = context;
409 /// <summary>Inlines or schedules the continuation.</summary>
410 /// <param name="ignored">The antecedent task, which is ignored.</param>
411 /// <param name="canInlineContinuationTask">true if inlining is permitted; otherwise, false.</param>
412 [SecuritySafeCritical]
413 internal sealed override void Run(Task task, bool canInlineContinuationTask)
415 // If we're allowed to inline, run the action on this thread.
416 if (canInlineContinuationTask &&
417 m_syncContext == SynchronizationContext.CurrentNoFlow)
419 RunCallback(GetInvokeActionCallback(), m_action, ref Task.t_currentTask);
421 // Otherwise, Post the action back to the SynchronizationContext.
425 TplEtwProvider etwLog = TplEtwProvider.Log;
426 if (etwLog.IsEnabled())
428 m_continuationId = Task.NewId();
429 etwLog.AwaitTaskContinuationScheduled((task.ExecutingTaskScheduler ?? TaskScheduler.Default).Id, task.Id, m_continuationId);
432 RunCallback(GetPostActionCallback(), this, ref Task.t_currentTask);
434 // Any exceptions will be handled by RunCallback.
437 /// <summary>Calls InvokeOrPostAction(false) on the supplied SynchronizationContextAwaitTaskContinuation.</summary>
438 /// <param name="state">The SynchronizationContextAwaitTaskContinuation.</param>
440 private static void PostAction(object state)
442 var c = (SynchronizationContextAwaitTaskContinuation)state;
444 TplEtwProvider etwLog = TplEtwProvider.Log;
445 if (etwLog.TasksSetActivityIds && c.m_continuationId != 0)
447 c.m_syncContext.Post(s_postCallback, GetActionLogDelegate(c.m_continuationId, c.m_action));
452 c.m_syncContext.Post(s_postCallback, c.m_action); // s_postCallback is manually cached, as the compiler won't in a SecurityCritical method
456 private static Action GetActionLogDelegate(int continuationId, Action action)
460 Guid savedActivityId;
461 Guid activityId = TplEtwProvider.CreateGuidForTaskID(continuationId);
462 System.Diagnostics.Tracing.EventSource.SetCurrentThreadActivityId(activityId, out savedActivityId);
464 finally { System.Diagnostics.Tracing.EventSource.SetCurrentThreadActivityId(savedActivityId); }
468 /// <summary>Gets a cached delegate for the PostAction method.</summary>
470 /// A delegate for PostAction, which expects a SynchronizationContextAwaitTaskContinuation
471 /// to be passed as state.
473 [MethodImpl(MethodImplOptions.AggressiveInlining)]
475 private static ContextCallback GetPostActionCallback()
477 ContextCallback callback = s_postActionCallback;
478 if (callback == null) { s_postActionCallback = callback = PostAction; } // lazily initialize SecurityCritical delegate
483 /// <summary>Task continuation for awaiting with a task scheduler.</summary>
484 internal sealed class TaskSchedulerAwaitTaskContinuation : AwaitTaskContinuation
486 /// <summary>The scheduler on which to run the action.</summary>
487 private readonly TaskScheduler m_scheduler;
489 /// <summary>Initializes the TaskSchedulerAwaitTaskContinuation.</summary>
490 /// <param name="scheduler">The task scheduler with which to invoke the action. Must not be null.</param>
491 /// <param name="action">The action to invoke. Must not be null.</param>
492 /// <param name="flowExecutionContext">Whether to capture and restore ExecutionContext.</param>
493 /// <param name="stackMark">The captured stack mark.</param>
495 internal TaskSchedulerAwaitTaskContinuation(
496 TaskScheduler scheduler, Action action, bool flowExecutionContext, ref StackCrawlMark stackMark) :
497 base(action, flowExecutionContext, ref stackMark)
499 Contract.Assert(scheduler != null);
500 m_scheduler = scheduler;
503 /// <summary>Inlines or schedules the continuation.</summary>
504 /// <param name="ignored">The antecedent task, which is ignored.</param>
505 /// <param name="canInlineContinuationTask">true if inlining is permitted; otherwise, false.</param>
506 internal sealed override void Run(Task ignored, bool canInlineContinuationTask)
508 // If we're targeting the default scheduler, we can use the faster path provided by the base class.
509 if (m_scheduler == TaskScheduler.Default)
511 base.Run(ignored, canInlineContinuationTask);
515 // We permit inlining if the caller allows us to, and
516 // either we're on a thread pool thread (in which case we're fine running arbitrary code)
517 // or we're already on the target scheduler (in which case we'll just ask the scheduler
518 // whether it's ok to run here). We include the IsThreadPoolThread check here, whereas
519 // we don't in AwaitTaskContinuation.Run, since here it expands what's allowed as opposed
520 // to in AwaitTaskContinuation.Run where it restricts what's allowed.
521 bool inlineIfPossible = canInlineContinuationTask &&
522 (TaskScheduler.InternalCurrent == m_scheduler || Thread.CurrentThread.IsThreadPoolThread);
524 // Create the continuation task task. If we're allowed to inline, try to do so.
525 // The target scheduler may still deny us from executing on this thread, in which case this'll be queued.
526 var task = CreateTask(state => {
527 try { ((Action)state)(); }
528 catch (Exception exc) { ThrowAsyncIfNecessary(exc); }
529 }, m_action, m_scheduler);
531 if (inlineIfPossible)
533 InlineIfPossibleOrElseQueue(task, needsProtection: false);
537 // We need to run asynchronously, so just schedule the task.
538 try { task.ScheduleAndStart(needsProtection: false); }
539 catch (TaskSchedulerException) { } // No further action is necessary, as ScheduleAndStart already transitioned task to faulted
545 /// <summary>Base task continuation class used for await continuations.</summary>
546 internal class AwaitTaskContinuation : TaskContinuation, IThreadPoolWorkItem
548 /// <summary>The ExecutionContext with which to run the continuation.</summary>
549 private readonly ExecutionContext m_capturedContext;
550 /// <summary>The action to invoke.</summary>
551 protected readonly Action m_action;
554 protected int m_continuationId;
557 /// <summary>Initializes the continuation.</summary>
558 /// <param name="action">The action to invoke. Must not be null.</param>
559 /// <param name="flowExecutionContext">Whether to capture and restore ExecutionContext.</param>
560 /// <param name="stackMark">The captured stack mark with which to construct an ExecutionContext.</param>
562 internal AwaitTaskContinuation(Action action, bool flowExecutionContext, ref StackCrawlMark stackMark)
564 Contract.Requires(action != null);
566 if (flowExecutionContext)
568 m_capturedContext = ExecutionContext.Capture(
570 ExecutionContext.CaptureOptions.IgnoreSyncCtx | ExecutionContext.CaptureOptions.OptimizeDefaultCase);
574 /// <summary>Initializes the continuation.</summary>
575 /// <param name="action">The action to invoke. Must not be null.</param>
576 /// <param name="flowExecutionContext">Whether to capture and restore ExecutionContext.</param>
578 internal AwaitTaskContinuation(Action action, bool flowExecutionContext)
580 Contract.Requires(action != null);
582 if (flowExecutionContext)
584 m_capturedContext = ExecutionContext.FastCapture();
588 /// <summary>Creates a task to run the action with the specified state on the specified scheduler.</summary>
589 /// <param name="action">The action to run. Must not be null.</param>
590 /// <param name="state">The state to pass to the action. Must not be null.</param>
591 /// <param name="scheduler">The scheduler to target.</param>
592 /// <returns>The created task.</returns>
593 protected Task CreateTask(Action<object> action, object state, TaskScheduler scheduler)
595 Contract.Requires(action != null);
596 Contract.Requires(scheduler != null);
599 action, state, null, default(CancellationToken),
600 TaskCreationOptions.None, InternalTaskOptions.QueuedByRuntime, scheduler)
602 CapturedContext = m_capturedContext
606 /// <summary>Inlines or schedules the continuation onto the default scheduler.</summary>
607 /// <param name="ignored">The antecedent task, which is ignored.</param>
608 /// <param name="canInlineContinuationTask">true if inlining is permitted; otherwise, false.</param>
609 [SecuritySafeCritical]
610 internal override void Run(Task task, bool canInlineContinuationTask)
612 // For the base AwaitTaskContinuation, we allow inlining if our caller allows it
613 // and if we're in a "valid location" for it. See the comments on
614 // IsValidLocationForInlining for more about what's valid. For performance
615 // reasons we would like to always inline, but we don't in some cases to avoid
616 // running arbitrary amounts of work in suspected "bad locations", like UI threads.
617 if (canInlineContinuationTask && IsValidLocationForInlining)
619 RunCallback(GetInvokeActionCallback(), m_action, ref Task.t_currentTask); // any exceptions from m_action will be handled by s_callbackRunAction
624 TplEtwProvider etwLog = TplEtwProvider.Log;
625 if (etwLog.IsEnabled())
627 m_continuationId = Task.NewId();
628 etwLog.AwaitTaskContinuationScheduled((task.ExecutingTaskScheduler ?? TaskScheduler.Default).Id, task.Id, m_continuationId);
631 // We couldn't inline, so now we need to schedule it
632 ThreadPool.UnsafeQueueCustomWorkItem(this, forceGlobal: false);
637 /// Gets whether the current thread is an appropriate location to inline a continuation's execution.
640 /// Returns whether SynchronizationContext is null and we're in the default scheduler.
641 /// If the await had a SynchronizationContext/TaskScheduler where it began and the
642 /// default/ConfigureAwait(true) was used, then we won't be on this path. If, however,
643 /// ConfigureAwait(false) was used, or the SynchronizationContext and TaskScheduler were
644 /// naturally null/Default, then we might end up here. If we do, we need to make sure
645 /// that we don't execute continuations in a place that isn't set up to handle them, e.g.
646 /// running arbitrary amounts of code on the UI thread. It would be "correct", but very
647 /// expensive, to always run the continuations asynchronously, incurring lots of context
648 /// switches and allocations and locks and the like. As such, we employ the heuristic
649 /// that if the current thread has a non-null SynchronizationContext or a non-default
650 /// scheduler, then we better not run arbitrary continuations here.
652 internal static bool IsValidLocationForInlining
656 // If there's a SynchronizationContext, we'll be conservative and say
657 // this is a bad location to inline.
658 var ctx = SynchronizationContext.CurrentNoFlow;
659 if (ctx != null && ctx.GetType() != typeof(SynchronizationContext)) return false;
661 // Similarly, if there's a non-default TaskScheduler, we'll be conservative
662 // and say this is a bad location to inline.
663 var sched = TaskScheduler.InternalCurrent;
664 return sched == null || sched == TaskScheduler.Default;
668 /// <summary>IThreadPoolWorkItem override, which is the entry function for this when the ThreadPool scheduler decides to run it.</summary>
670 void ExecuteWorkItemHelper()
673 var etwLog = TplEtwProvider.Log;
674 Guid savedActivityId = Guid.Empty;
675 if (etwLog.TasksSetActivityIds && m_continuationId != 0)
677 Guid activityId = TplEtwProvider.CreateGuidForTaskID(m_continuationId);
678 System.Diagnostics.Tracing.EventSource.SetCurrentThreadActivityId(activityId, out savedActivityId);
683 // We're not inside of a task, so t_currentTask doesn't need to be specially maintained.
684 // We're on a thread pool thread with no higher-level callers, so exceptions can just propagate.
686 // If there's no execution context, just invoke the delegate.
687 if (m_capturedContext == null)
691 // If there is an execution context, get the cached delegate and run the action under the context.
696 ExecutionContext.Run(m_capturedContext, GetInvokeActionCallback(), m_action, true);
698 finally { m_capturedContext.Dispose(); }
704 if (etwLog.TasksSetActivityIds && m_continuationId != 0)
706 System.Diagnostics.Tracing.EventSource.SetCurrentThreadActivityId(savedActivityId);
713 void IThreadPoolWorkItem.ExecuteWorkItem()
715 // inline the fast path
716 if (m_capturedContext == null
718 && !TplEtwProvider.Log.IsEnabled()
726 ExecuteWorkItemHelper();
731 /// The ThreadPool calls this if a ThreadAbortException is thrown while trying to execute this workitem.
734 void IThreadPoolWorkItem.MarkAborted(ThreadAbortException tae) { /* nop */ }
736 /// <summary>Cached delegate that invokes an Action passed as an object parameter.</summary>
738 private static ContextCallback s_invokeActionCallback;
740 /// <summary>Runs an action provided as an object parameter.</summary>
741 /// <param name="state">The Action to invoke.</param>
743 private static void InvokeAction(object state) { ((Action)state)(); }
745 [MethodImpl(MethodImplOptions.AggressiveInlining)]
747 protected static ContextCallback GetInvokeActionCallback()
749 ContextCallback callback = s_invokeActionCallback;
750 if (callback == null) { s_invokeActionCallback = callback = InvokeAction; } // lazily initialize SecurityCritical delegate
754 /// <summary>Runs the callback synchronously with the provided state.</summary>
755 /// <param name="callback">The callback to run.</param>
756 /// <param name="state">The state to pass to the callback.</param>
757 /// <param name="currentTask">A reference to Task.t_currentTask.</param>
759 protected void RunCallback(ContextCallback callback, object state, ref Task currentTask)
761 Contract.Requires(callback != null);
762 Contract.Assert(currentTask == Task.t_currentTask);
764 // Pretend there's no current task, so that no task is seen as a parent
765 // and TaskScheduler.Current does not reflect false information
766 var prevCurrentTask = currentTask;
769 if (prevCurrentTask != null) currentTask = null;
771 // If there's no captured context, just run the callback directly.
772 if (m_capturedContext == null) callback(state);
773 // Otherwise, use the captured context to do so.
774 else ExecutionContext.Run(m_capturedContext, callback, state, true);
776 catch (Exception exc) // we explicitly do not request handling of dangerous exceptions like AVs
778 ThrowAsyncIfNecessary(exc);
782 // Restore the current task information
783 if (prevCurrentTask != null) currentTask = prevCurrentTask;
785 // Clean up after the execution context, which is only usable once.
786 if (m_capturedContext != null) m_capturedContext.Dispose();
790 /// <summary>Invokes or schedules the action to be executed.</summary>
791 /// <param name="action">The action to invoke or queue.</param>
792 /// <param name="allowInlining">
793 /// true to allow inlining, or false to force the action to run asynchronously.
795 /// <param name="currentTask">
796 /// A reference to the t_currentTask thread static value.
797 /// This is passed by-ref rather than accessed in the method in order to avoid
798 /// unnecessary thread-static writes.
801 /// No ExecutionContext work is performed used. This method is only used in the
802 /// case where a raw Action continuation delegate was stored into the Task, which
803 /// only happens in Task.SetContinuationForAwait if execution context flow was disabled
804 /// via using TaskAwaiter.UnsafeOnCompleted or a similar path.
807 internal static void RunOrScheduleAction(Action action, bool allowInlining, ref Task currentTask)
809 Contract.Assert(currentTask == Task.t_currentTask);
811 // If we're not allowed to run here, schedule the action
812 if (!allowInlining || !IsValidLocationForInlining)
814 UnsafeScheduleAction(action, currentTask);
818 // Otherwise, run it, making sure that t_currentTask is null'd out appropriately during the execution
819 Task prevCurrentTask = currentTask;
822 if (prevCurrentTask != null) currentTask = null;
825 catch (Exception exception)
827 ThrowAsyncIfNecessary(exception);
831 if (prevCurrentTask != null) currentTask = prevCurrentTask;
835 /// <summary>Schedules the action to be executed. No ExecutionContext work is performed used.</summary>
836 /// <param name="action">The action to invoke or queue.</param>
838 internal static void UnsafeScheduleAction(Action action, Task task)
840 AwaitTaskContinuation atc = new AwaitTaskContinuation(action, flowExecutionContext: false);
842 var etwLog = TplEtwProvider.Log;
843 if (etwLog.IsEnabled() && task != null)
845 atc.m_continuationId = Task.NewId();
846 etwLog.AwaitTaskContinuationScheduled((task.ExecutingTaskScheduler ?? TaskScheduler.Default).Id, task.Id, atc.m_continuationId);
849 ThreadPool.UnsafeQueueCustomWorkItem(atc, forceGlobal: false);
852 /// <summary>Throws the exception asynchronously on the ThreadPool.</summary>
853 /// <param name="exc">The exception to throw.</param>
854 protected static void ThrowAsyncIfNecessary(Exception exc)
856 // Awaits should never experience an exception (other than an TAE or ADUE),
857 // unless a malicious user is explicitly passing a throwing action into the TaskAwaiter.
858 // We don't want to allow the exception to propagate on this stack, as it'll emerge in random places,
859 // and we can't fail fast, as that would allow for elevation of privilege.
861 // If unhandled error reporting APIs are available use those, otherwise since this
862 // would have executed on the thread pool otherwise, let it propagate there.
864 if (!(exc is ThreadAbortException || exc is AppDomainUnloadedException))
866 #if FEATURE_COMINTEROP
867 if (!WindowsRuntimeMarshal.ReportUnhandledError(exc))
868 #endif // FEATURE_COMINTEROP
870 var edi = ExceptionDispatchInfo.Capture(exc);
871 ThreadPool.QueueUserWorkItem(s => ((ExceptionDispatchInfo)s).Throw(), edi);
876 internal override Delegate[] GetDelegateContinuationsForDebugger()
878 Contract.Assert(m_action != null);
879 return new Delegate[] { AsyncMethodBuilderCore.TryGetStateMachineForDebugger(m_action) };