1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
5 // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
9 // Implementation of task continuations, TaskContinuation, and its descendants.
11 // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
13 using System.Security;
14 using System.Diagnostics;
15 using System.Runtime.ExceptionServices;
16 using System.Runtime.CompilerServices;
17 using System.Threading;
19 #if FEATURE_COMINTEROP
20 using System.Runtime.InteropServices.WindowsRuntime;
21 #endif // FEATURE_COMINTEROP
23 namespace System.Threading.Tasks
25 // Task type used to implement: Task ContinueWith(Action<Task,...>)
26 internal sealed class ContinuationTaskFromTask : Task
28 private Task m_antecedent;
30 public ContinuationTaskFromTask(
31 Task antecedent, Delegate action, object state, TaskCreationOptions creationOptions, InternalTaskOptions internalOptions) :
32 base(action, state, Task.InternalCurrentIfAttached(creationOptions), default(CancellationToken), creationOptions, internalOptions, null)
34 Debug.Assert(action is Action<Task> || action is Action<Task, object>,
35 "Invalid delegate type in ContinuationTaskFromTask");
36 m_antecedent = antecedent;
40 /// Evaluates the value selector of the Task which is passed in as an object and stores the result.
42 internal override void InnerInvoke()
44 // Get and null out the antecedent. This is crucial to avoid a memory
45 // leak with long chains of continuations.
46 var antecedent = m_antecedent;
47 Debug.Assert(antecedent != null,
48 "No antecedent was set for the ContinuationTaskFromTask.");
51 // Notify the debugger we're completing an asynchronous wait on a task
52 antecedent.NotifyDebuggerOfWaitCompletionIfNecessary();
54 // Invoke the delegate
55 Debug.Assert(m_action != null);
56 var action = m_action as Action<Task>;
62 var actionWithState = m_action as Action<Task, object>;
63 if (actionWithState != null)
65 actionWithState(antecedent, m_stateObject);
68 Debug.Fail("Invalid m_action in ContinuationTaskFromTask");
72 // Task type used to implement: Task<TResult> ContinueWith(Func<Task,...>)
73 internal sealed class ContinuationResultTaskFromTask<TResult> : Task<TResult>
75 private Task m_antecedent;
77 public ContinuationResultTaskFromTask(
78 Task antecedent, Delegate function, object state, TaskCreationOptions creationOptions, InternalTaskOptions internalOptions) :
79 base(function, state, Task.InternalCurrentIfAttached(creationOptions), default(CancellationToken), creationOptions, internalOptions, null)
81 Debug.Assert(function is Func<Task, TResult> || function is Func<Task, object, TResult>,
82 "Invalid delegate type in ContinuationResultTaskFromTask");
83 m_antecedent = antecedent;
87 /// Evaluates the value selector of the Task which is passed in as an object and stores the result.
89 internal override void InnerInvoke()
91 // Get and null out the antecedent. This is crucial to avoid a memory
92 // leak with long chains of continuations.
93 var antecedent = m_antecedent;
94 Debug.Assert(antecedent != null,
95 "No antecedent was set for the ContinuationResultTaskFromTask.");
98 // Notify the debugger we're completing an asynchronous wait on a task
99 antecedent.NotifyDebuggerOfWaitCompletionIfNecessary();
101 // Invoke the delegate
102 Debug.Assert(m_action != null);
103 var func = m_action as Func<Task, TResult>;
106 m_result = func(antecedent);
109 var funcWithState = m_action as Func<Task, object, TResult>;
110 if (funcWithState != null)
112 m_result = funcWithState(antecedent, m_stateObject);
115 Debug.Fail("Invalid m_action in ContinuationResultTaskFromTask");
119 // Task type used to implement: Task ContinueWith(Action<Task<TAntecedentResult>,...>)
120 internal sealed class ContinuationTaskFromResultTask<TAntecedentResult> : Task
122 private Task<TAntecedentResult> m_antecedent;
124 public ContinuationTaskFromResultTask(
125 Task<TAntecedentResult> antecedent, Delegate action, object state, TaskCreationOptions creationOptions, InternalTaskOptions internalOptions) :
126 base(action, state, Task.InternalCurrentIfAttached(creationOptions), default(CancellationToken), creationOptions, internalOptions, null)
128 Debug.Assert(action is Action<Task<TAntecedentResult>> || action is Action<Task<TAntecedentResult>, object>,
129 "Invalid delegate type in ContinuationTaskFromResultTask");
130 m_antecedent = antecedent;
134 /// Evaluates the value selector of the Task which is passed in as an object and stores the result.
136 internal override void InnerInvoke()
138 // Get and null out the antecedent. This is crucial to avoid a memory
139 // leak with long chains of continuations.
140 var antecedent = m_antecedent;
141 Debug.Assert(antecedent != null,
142 "No antecedent was set for the ContinuationTaskFromResultTask.");
145 // Notify the debugger we're completing an asynchronous wait on a task
146 antecedent.NotifyDebuggerOfWaitCompletionIfNecessary();
148 // Invoke the delegate
149 Debug.Assert(m_action != null);
150 var action = m_action as Action<Task<TAntecedentResult>>;
156 var actionWithState = m_action as Action<Task<TAntecedentResult>, object>;
157 if (actionWithState != null)
159 actionWithState(antecedent, m_stateObject);
162 Debug.Fail("Invalid m_action in ContinuationTaskFromResultTask");
166 // Task type used to implement: Task<TResult> ContinueWith(Func<Task<TAntecedentResult>,...>)
167 internal sealed class ContinuationResultTaskFromResultTask<TAntecedentResult, TResult> : Task<TResult>
169 private Task<TAntecedentResult> m_antecedent;
171 public ContinuationResultTaskFromResultTask(
172 Task<TAntecedentResult> antecedent, Delegate function, object state, TaskCreationOptions creationOptions, InternalTaskOptions internalOptions) :
173 base(function, state, Task.InternalCurrentIfAttached(creationOptions), default(CancellationToken), creationOptions, internalOptions, null)
175 Debug.Assert(function is Func<Task<TAntecedentResult>, TResult> || function is Func<Task<TAntecedentResult>, object, TResult>,
176 "Invalid delegate type in ContinuationResultTaskFromResultTask");
177 m_antecedent = antecedent;
181 /// Evaluates the value selector of the Task which is passed in as an object and stores the result.
183 internal override void InnerInvoke()
185 // Get and null out the antecedent. This is crucial to avoid a memory
186 // leak with long chains of continuations.
187 var antecedent = m_antecedent;
188 Debug.Assert(antecedent != null,
189 "No antecedent was set for the ContinuationResultTaskFromResultTask.");
192 // Notify the debugger we're completing an asynchronous wait on a task
193 antecedent.NotifyDebuggerOfWaitCompletionIfNecessary();
195 // Invoke the delegate
196 Debug.Assert(m_action != null);
197 var func = m_action as Func<Task<TAntecedentResult>, TResult>;
200 m_result = func(antecedent);
203 var funcWithState = m_action as Func<Task<TAntecedentResult>, object, TResult>;
204 if (funcWithState != null)
206 m_result = funcWithState(antecedent, m_stateObject);
209 Debug.Fail("Invalid m_action in ContinuationResultTaskFromResultTask");
213 // For performance reasons, we don't just have a single way of representing
214 // a continuation object. Rather, we have a hierarchy of types:
215 // - TaskContinuation: abstract base that provides a virtual Run method
216 // - StandardTaskContinuation: wraps a task,options,and scheduler, and overrides Run to process the task with that configuration
217 // - AwaitTaskContinuation: base for continuations created through TaskAwaiter; targets default scheduler by default
218 // - TaskSchedulerAwaitTaskContinuation: awaiting with a non-default TaskScheduler
219 // - SynchronizationContextAwaitTaskContinuation: awaiting with a "current" sync ctx
221 /// <summary>Represents a continuation.</summary>
222 internal abstract class TaskContinuation
224 /// <summary>Inlines or schedules the continuation.</summary>
225 /// <param name="completedTask">The antecedent task that has completed.</param>
226 /// <param name="canInlineContinuationTask">true if inlining is permitted; otherwise, false.</param>
227 internal abstract void Run(Task completedTask, bool bCanInlineContinuationTask);
229 /// <summary>Tries to run the task on the current thread, if possible; otherwise, schedules it.</summary>
230 /// <param name="task">The task to run</param>
231 /// <param name="needsProtection">
232 /// true if we need to protect against multiple threads racing to start/cancel the task; otherwise, false.
234 protected static void InlineIfPossibleOrElseQueue(Task task, bool needsProtection)
236 Debug.Assert(task != null);
237 Debug.Assert(task.m_taskScheduler != null);
239 // Set the TASK_STATE_STARTED flag. This only needs to be done
240 // if the task may be canceled or if someone else has a reference to it
241 // that may try to execute it.
244 if (!task.MarkStarted())
245 return; // task has been previously started or canceled. Stop processing.
249 task.m_stateFlags |= Task.TASK_STATE_STARTED;
252 // Try to inline it but queue if we can't
255 if (!task.m_taskScheduler.TryRunInline(task, taskWasPreviouslyQueued: false))
257 task.m_taskScheduler.InternalQueueTask(task);
262 // Either TryRunInline() or QueueTask() threw an exception. Record the exception, marking the task as Faulted.
263 // However if it was a ThreadAbortException coming from TryRunInline we need to skip here,
264 // because it would already have been handled in Task.Execute()
265 if (!(e is ThreadAbortException &&
266 (task.m_stateFlags & Task.TASK_STATE_THREAD_WAS_ABORTED) != 0)) // this ensures TAEs from QueueTask will be wrapped in TSE
268 TaskSchedulerException tse = new TaskSchedulerException(e);
269 task.AddException(tse);
277 internal abstract Delegate[] GetDelegateContinuationsForDebugger();
280 /// <summary>Provides the standard implementation of a task continuation.</summary>
281 internal class StandardTaskContinuation : TaskContinuation
283 /// <summary>The unstarted continuation task.</summary>
284 internal readonly Task m_task;
285 /// <summary>The options to use with the continuation task.</summary>
286 internal readonly TaskContinuationOptions m_options;
287 /// <summary>The task scheduler with which to run the continuation task.</summary>
288 private readonly TaskScheduler m_taskScheduler;
290 /// <summary>Initializes a new continuation.</summary>
291 /// <param name="task">The task to be activated.</param>
292 /// <param name="options">The continuation options.</param>
293 /// <param name="scheduler">The scheduler to use for the continuation.</param>
294 internal StandardTaskContinuation(Task task, TaskContinuationOptions options, TaskScheduler scheduler)
296 Debug.Assert(task != null, "TaskContinuation ctor: task is null");
297 Debug.Assert(scheduler != null, "TaskContinuation ctor: scheduler is null");
300 m_taskScheduler = scheduler;
301 if (AsyncCausalityTracer.LoggingOn)
302 AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, m_task.Id, "Task.ContinueWith: " + task.m_action.Method.Name, 0);
304 if (Task.s_asyncDebuggingEnabled)
306 Task.AddToActiveTasks(m_task);
310 /// <summary>Invokes the continuation for the target completion task.</summary>
311 /// <param name="completedTask">The completed task.</param>
312 /// <param name="bCanInlineContinuationTask">Whether the continuation can be inlined.</param>
313 internal override void Run(Task completedTask, bool bCanInlineContinuationTask)
315 Debug.Assert(completedTask != null);
316 Debug.Assert(completedTask.IsCompleted, "ContinuationTask.Run(): completedTask not completed");
318 // Check if the completion status of the task works with the desired
319 // activation criteria of the TaskContinuationOptions.
320 TaskContinuationOptions options = m_options;
322 completedTask.IsCompletedSuccessfully ?
323 (options & TaskContinuationOptions.NotOnRanToCompletion) == 0 :
324 (completedTask.IsCanceled ?
325 (options & TaskContinuationOptions.NotOnCanceled) == 0 :
326 (options & TaskContinuationOptions.NotOnFaulted) == 0);
328 // If the completion status is allowed, run the continuation.
329 Task continuationTask = m_task;
332 //If the task was cancel before running (e.g a ContinueWhenAll with a cancelled caancelation token)
333 //we will still flow it to ScheduleAndStart() were it will check the status before running
334 //We check here to avoid faulty logs that contain a join event to an operation that was already set as completed.
335 if (!continuationTask.IsCanceled && AsyncCausalityTracer.LoggingOn)
337 // Log now that we are sure that this continuation is being ran
338 AsyncCausalityTracer.TraceOperationRelation(CausalityTraceLevel.Important, continuationTask.Id, CausalityRelation.AssignDelegate);
340 continuationTask.m_taskScheduler = m_taskScheduler;
342 // Either run directly or just queue it up for execution, depending
343 // on whether synchronous or asynchronous execution is wanted.
344 if (bCanInlineContinuationTask && // inlining is allowed by the caller
345 (options & TaskContinuationOptions.ExecuteSynchronously) != 0) // synchronous execution was requested by the continuation's creator
347 InlineIfPossibleOrElseQueue(continuationTask, needsProtection: true);
351 try { continuationTask.ScheduleAndStart(needsProtection: true); }
352 catch (TaskSchedulerException)
354 // No further action is necessary -- ScheduleAndStart() already transitioned the
355 // task to faulted. But we want to make sure that no exception is thrown from here.
359 // Otherwise, the final state of this task does not match the desired
360 // continuation activation criteria; cancel it to denote this.
361 else continuationTask.InternalCancel(false);
364 internal override Delegate[] GetDelegateContinuationsForDebugger()
366 if (m_task.m_action == null)
368 return m_task.GetDelegateContinuationsForDebugger();
371 return new Delegate[] { m_task.m_action };
375 /// <summary>Task continuation for awaiting with a current synchronization context.</summary>
376 internal sealed class SynchronizationContextAwaitTaskContinuation : AwaitTaskContinuation
378 /// <summary>SendOrPostCallback delegate to invoke the action.</summary>
379 private readonly static SendOrPostCallback s_postCallback = state => ((Action)state)(); // can't use InvokeAction as it's SecurityCritical
380 /// <summary>Cached delegate for PostAction</summary>
381 private static ContextCallback s_postActionCallback;
382 /// <summary>The context with which to run the action.</summary>
383 private readonly SynchronizationContext m_syncContext;
385 /// <summary>Initializes the SynchronizationContextAwaitTaskContinuation.</summary>
386 /// <param name="context">The synchronization context with which to invoke the action. Must not be null.</param>
387 /// <param name="action">The action to invoke. Must not be null.</param>
388 /// <param name="flowExecutionContext">Whether to capture and restore ExecutionContext.</param>
389 internal SynchronizationContextAwaitTaskContinuation(
390 SynchronizationContext context, Action action, bool flowExecutionContext) :
391 base(action, flowExecutionContext)
393 Debug.Assert(context != null);
394 m_syncContext = context;
397 /// <summary>Inlines or schedules the continuation.</summary>
398 /// <param name="ignored">The antecedent task, which is ignored.</param>
399 /// <param name="canInlineContinuationTask">true if inlining is permitted; otherwise, false.</param>
400 internal sealed override void Run(Task task, bool canInlineContinuationTask)
402 // If we're allowed to inline, run the action on this thread.
403 if (canInlineContinuationTask &&
404 m_syncContext == SynchronizationContext.Current)
406 RunCallback(GetInvokeActionCallback(), m_action, ref Task.t_currentTask);
408 // Otherwise, Post the action back to the SynchronizationContext.
411 TplEtwProvider etwLog = TplEtwProvider.Log;
412 if (etwLog.IsEnabled())
414 m_continuationId = Task.NewId();
415 etwLog.AwaitTaskContinuationScheduled((task.ExecutingTaskScheduler ?? TaskScheduler.Default).Id, task.Id, m_continuationId);
417 RunCallback(GetPostActionCallback(), this, ref Task.t_currentTask);
419 // Any exceptions will be handled by RunCallback.
422 /// <summary>Calls InvokeOrPostAction(false) on the supplied SynchronizationContextAwaitTaskContinuation.</summary>
423 /// <param name="state">The SynchronizationContextAwaitTaskContinuation.</param>
424 private static void PostAction(object state)
426 var c = (SynchronizationContextAwaitTaskContinuation)state;
428 TplEtwProvider etwLog = TplEtwProvider.Log;
429 if (etwLog.TasksSetActivityIds && c.m_continuationId != 0)
431 c.m_syncContext.Post(s_postCallback, GetActionLogDelegate(c.m_continuationId, c.m_action));
435 c.m_syncContext.Post(s_postCallback, c.m_action); // s_postCallback is manually cached, as the compiler won't in a SecurityCritical method
439 private static Action GetActionLogDelegate(int continuationId, Action action)
443 Guid savedActivityId;
444 Guid activityId = TplEtwProvider.CreateGuidForTaskID(continuationId);
445 System.Diagnostics.Tracing.EventSource.SetCurrentThreadActivityId(activityId, out savedActivityId);
447 finally { System.Diagnostics.Tracing.EventSource.SetCurrentThreadActivityId(savedActivityId); }
451 /// <summary>Gets a cached delegate for the PostAction method.</summary>
453 /// A delegate for PostAction, which expects a SynchronizationContextAwaitTaskContinuation
454 /// to be passed as state.
456 [MethodImpl(MethodImplOptions.AggressiveInlining)]
457 private static ContextCallback GetPostActionCallback()
459 ContextCallback callback = s_postActionCallback;
460 if (callback == null) { s_postActionCallback = callback = PostAction; } // lazily initialize SecurityCritical delegate
465 /// <summary>Task continuation for awaiting with a task scheduler.</summary>
466 internal sealed class TaskSchedulerAwaitTaskContinuation : AwaitTaskContinuation
468 /// <summary>The scheduler on which to run the action.</summary>
469 private readonly TaskScheduler m_scheduler;
471 /// <summary>Initializes the TaskSchedulerAwaitTaskContinuation.</summary>
472 /// <param name="scheduler">The task scheduler with which to invoke the action. Must not be null.</param>
473 /// <param name="action">The action to invoke. Must not be null.</param>
474 /// <param name="flowExecutionContext">Whether to capture and restore ExecutionContext.</param>
475 internal TaskSchedulerAwaitTaskContinuation(
476 TaskScheduler scheduler, Action action, bool flowExecutionContext) :
477 base(action, flowExecutionContext)
479 Debug.Assert(scheduler != null);
480 m_scheduler = scheduler;
483 /// <summary>Inlines or schedules the continuation.</summary>
484 /// <param name="ignored">The antecedent task, which is ignored.</param>
485 /// <param name="canInlineContinuationTask">true if inlining is permitted; otherwise, false.</param>
486 internal sealed override void Run(Task ignored, bool canInlineContinuationTask)
488 // If we're targeting the default scheduler, we can use the faster path provided by the base class.
489 if (m_scheduler == TaskScheduler.Default)
491 base.Run(ignored, canInlineContinuationTask);
495 // We permit inlining if the caller allows us to, and
496 // either we're on a thread pool thread (in which case we're fine running arbitrary code)
497 // or we're already on the target scheduler (in which case we'll just ask the scheduler
498 // whether it's ok to run here). We include the IsThreadPoolThread check here, whereas
499 // we don't in AwaitTaskContinuation.Run, since here it expands what's allowed as opposed
500 // to in AwaitTaskContinuation.Run where it restricts what's allowed.
501 bool inlineIfPossible = canInlineContinuationTask &&
502 (TaskScheduler.InternalCurrent == m_scheduler || Thread.CurrentThread.IsThreadPoolThread);
504 // Create the continuation task task. If we're allowed to inline, try to do so.
505 // The target scheduler may still deny us from executing on this thread, in which case this'll be queued.
506 var task = CreateTask(state =>
508 try { ((Action)state)(); }
509 catch (Exception exc) { ThrowAsyncIfNecessary(exc); }
510 }, m_action, m_scheduler);
512 if (inlineIfPossible)
514 InlineIfPossibleOrElseQueue(task, needsProtection: false);
518 // We need to run asynchronously, so just schedule the task.
519 try { task.ScheduleAndStart(needsProtection: false); }
520 catch (TaskSchedulerException) { } // No further action is necessary, as ScheduleAndStart already transitioned task to faulted
526 /// <summary>Base task continuation class used for await continuations.</summary>
527 internal class AwaitTaskContinuation : TaskContinuation, IThreadPoolWorkItem
529 /// <summary>The ExecutionContext with which to run the continuation.</summary>
530 private readonly ExecutionContext m_capturedContext;
531 /// <summary>The action to invoke.</summary>
532 protected readonly Action m_action;
534 protected int m_continuationId;
536 /// <summary>Initializes the continuation.</summary>
537 /// <param name="action">The action to invoke. Must not be null.</param>
538 /// <param name="flowExecutionContext">Whether to capture and restore ExecutionContext.</param>
539 internal AwaitTaskContinuation(Action action, bool flowExecutionContext)
541 Debug.Assert(action != null);
543 if (flowExecutionContext)
545 m_capturedContext = ExecutionContext.Capture();
549 /// <summary>Creates a task to run the action with the specified state on the specified scheduler.</summary>
550 /// <param name="action">The action to run. Must not be null.</param>
551 /// <param name="state">The state to pass to the action. Must not be null.</param>
552 /// <param name="scheduler">The scheduler to target.</param>
553 /// <returns>The created task.</returns>
554 protected Task CreateTask(Action<object> action, object state, TaskScheduler scheduler)
556 Debug.Assert(action != null);
557 Debug.Assert(scheduler != null);
560 action, state, null, default(CancellationToken),
561 TaskCreationOptions.None, InternalTaskOptions.QueuedByRuntime, scheduler)
563 CapturedContext = m_capturedContext
567 /// <summary>Inlines or schedules the continuation onto the default scheduler.</summary>
568 /// <param name="ignored">The antecedent task, which is ignored.</param>
569 /// <param name="canInlineContinuationTask">true if inlining is permitted; otherwise, false.</param>
570 internal override void Run(Task task, bool canInlineContinuationTask)
572 // For the base AwaitTaskContinuation, we allow inlining if our caller allows it
573 // and if we're in a "valid location" for it. See the comments on
574 // IsValidLocationForInlining for more about what's valid. For performance
575 // reasons we would like to always inline, but we don't in some cases to avoid
576 // running arbitrary amounts of work in suspected "bad locations", like UI threads.
577 if (canInlineContinuationTask && IsValidLocationForInlining)
579 RunCallback(GetInvokeActionCallback(), m_action, ref Task.t_currentTask); // any exceptions from m_action will be handled by s_callbackRunAction
583 TplEtwProvider etwLog = TplEtwProvider.Log;
584 if (etwLog.IsEnabled())
586 m_continuationId = Task.NewId();
587 etwLog.AwaitTaskContinuationScheduled((task.ExecutingTaskScheduler ?? TaskScheduler.Default).Id, task.Id, m_continuationId);
590 // We couldn't inline, so now we need to schedule it
591 ThreadPool.UnsafeQueueCustomWorkItem(this, forceGlobal: false);
596 /// Gets whether the current thread is an appropriate location to inline a continuation's execution.
599 /// Returns whether SynchronizationContext is null and we're in the default scheduler.
600 /// If the await had a SynchronizationContext/TaskScheduler where it began and the
601 /// default/ConfigureAwait(true) was used, then we won't be on this path. If, however,
602 /// ConfigureAwait(false) was used, or the SynchronizationContext and TaskScheduler were
603 /// naturally null/Default, then we might end up here. If we do, we need to make sure
604 /// that we don't execute continuations in a place that isn't set up to handle them, e.g.
605 /// running arbitrary amounts of code on the UI thread. It would be "correct", but very
606 /// expensive, to always run the continuations asynchronously, incurring lots of context
607 /// switches and allocations and locks and the like. As such, we employ the heuristic
608 /// that if the current thread has a non-null SynchronizationContext or a non-default
609 /// scheduler, then we better not run arbitrary continuations here.
611 internal static bool IsValidLocationForInlining
615 // If there's a SynchronizationContext, we'll be conservative and say
616 // this is a bad location to inline.
617 var ctx = SynchronizationContext.Current;
618 if (ctx != null && ctx.GetType() != typeof(SynchronizationContext)) return false;
620 // Similarly, if there's a non-default TaskScheduler, we'll be conservative
621 // and say this is a bad location to inline.
622 var sched = TaskScheduler.InternalCurrent;
623 return sched == null || sched == TaskScheduler.Default;
627 /// <summary>IThreadPoolWorkItem override, which is the entry function for this when the ThreadPool scheduler decides to run it.</summary>
628 private void ExecuteWorkItemHelper()
630 var etwLog = TplEtwProvider.Log;
631 Guid savedActivityId = Guid.Empty;
632 if (etwLog.TasksSetActivityIds && m_continuationId != 0)
634 Guid activityId = TplEtwProvider.CreateGuidForTaskID(m_continuationId);
635 System.Diagnostics.Tracing.EventSource.SetCurrentThreadActivityId(activityId, out savedActivityId);
639 // We're not inside of a task, so t_currentTask doesn't need to be specially maintained.
640 // We're on a thread pool thread with no higher-level callers, so exceptions can just propagate.
642 // If there's no execution context, just invoke the delegate.
643 if (m_capturedContext == null)
647 // If there is an execution context, get the cached delegate and run the action under the context.
650 ExecutionContext.Run(m_capturedContext, GetInvokeActionCallback(), m_action);
655 if (etwLog.TasksSetActivityIds && m_continuationId != 0)
657 System.Diagnostics.Tracing.EventSource.SetCurrentThreadActivityId(savedActivityId);
662 void IThreadPoolWorkItem.ExecuteWorkItem()
664 // inline the fast path
665 if (m_capturedContext == null && !TplEtwProvider.Log.IsEnabled())
671 ExecuteWorkItemHelper();
676 /// The ThreadPool calls this if a ThreadAbortException is thrown while trying to execute this workitem.
678 void IThreadPoolWorkItem.MarkAborted(ThreadAbortException tae) { /* nop */ }
680 /// <summary>Cached delegate that invokes an Action passed as an object parameter.</summary>
681 private static ContextCallback s_invokeActionCallback;
683 /// <summary>Runs an action provided as an object parameter.</summary>
684 /// <param name="state">The Action to invoke.</param>
685 private static void InvokeAction(object state) { ((Action)state)(); }
687 [MethodImpl(MethodImplOptions.AggressiveInlining)]
688 protected static ContextCallback GetInvokeActionCallback()
690 ContextCallback callback = s_invokeActionCallback;
691 if (callback == null) { s_invokeActionCallback = callback = InvokeAction; } // lazily initialize SecurityCritical delegate
695 /// <summary>Runs the callback synchronously with the provided state.</summary>
696 /// <param name="callback">The callback to run.</param>
697 /// <param name="state">The state to pass to the callback.</param>
698 /// <param name="currentTask">A reference to Task.t_currentTask.</param>
699 protected void RunCallback(ContextCallback callback, object state, ref Task currentTask)
701 Debug.Assert(callback != null);
702 Debug.Assert(currentTask == Task.t_currentTask);
704 // Pretend there's no current task, so that no task is seen as a parent
705 // and TaskScheduler.Current does not reflect false information
706 var prevCurrentTask = currentTask;
709 if (prevCurrentTask != null) currentTask = null;
711 // If there's no captured context, just run the callback directly.
712 if (m_capturedContext == null) callback(state);
713 // Otherwise, use the captured context to do so.
714 else ExecutionContext.Run(m_capturedContext, callback, state);
716 catch (Exception exc) // we explicitly do not request handling of dangerous exceptions like AVs
718 ThrowAsyncIfNecessary(exc);
722 // Restore the current task information
723 if (prevCurrentTask != null) currentTask = prevCurrentTask;
727 /// <summary>Invokes or schedules the action to be executed.</summary>
728 /// <param name="action">The action to invoke or queue.</param>
729 /// <param name="allowInlining">
730 /// true to allow inlining, or false to force the action to run asynchronously.
732 /// <param name="currentTask">
733 /// A reference to the t_currentTask thread static value.
734 /// This is passed by-ref rather than accessed in the method in order to avoid
735 /// unnecessary thread-static writes.
738 /// No ExecutionContext work is performed used. This method is only used in the
739 /// case where a raw Action continuation delegate was stored into the Task, which
740 /// only happens in Task.SetContinuationForAwait if execution context flow was disabled
741 /// via using TaskAwaiter.UnsafeOnCompleted or a similar path.
743 internal static void RunOrScheduleAction(Action action, bool allowInlining, ref Task currentTask)
745 Debug.Assert(currentTask == Task.t_currentTask);
747 // If we're not allowed to run here, schedule the action
748 if (!allowInlining || !IsValidLocationForInlining)
750 UnsafeScheduleAction(action, currentTask);
754 // Otherwise, run it, making sure that t_currentTask is null'd out appropriately during the execution
755 Task prevCurrentTask = currentTask;
758 if (prevCurrentTask != null) currentTask = null;
761 catch (Exception exception)
763 ThrowAsyncIfNecessary(exception);
767 if (prevCurrentTask != null) currentTask = prevCurrentTask;
771 /// <summary>Schedules the action to be executed. No ExecutionContext work is performed used.</summary>
772 /// <param name="action">The action to invoke or queue.</param>
773 internal static void UnsafeScheduleAction(Action action, Task task)
775 AwaitTaskContinuation atc = new AwaitTaskContinuation(action, flowExecutionContext: false);
777 var etwLog = TplEtwProvider.Log;
778 if (etwLog.IsEnabled() && task != null)
780 atc.m_continuationId = Task.NewId();
781 etwLog.AwaitTaskContinuationScheduled((task.ExecutingTaskScheduler ?? TaskScheduler.Default).Id, task.Id, atc.m_continuationId);
784 ThreadPool.UnsafeQueueCustomWorkItem(atc, forceGlobal: false);
787 /// <summary>Throws the exception asynchronously on the ThreadPool.</summary>
788 /// <param name="exc">The exception to throw.</param>
789 protected static void ThrowAsyncIfNecessary(Exception exc)
791 // Awaits should never experience an exception (other than an TAE or ADUE),
792 // unless a malicious user is explicitly passing a throwing action into the TaskAwaiter.
793 // We don't want to allow the exception to propagate on this stack, as it'll emerge in random places,
794 // and we can't fail fast, as that would allow for elevation of privilege.
796 // If unhandled error reporting APIs are available use those, otherwise since this
797 // would have executed on the thread pool otherwise, let it propagate there.
799 if (!(exc is ThreadAbortException || exc is AppDomainUnloadedException))
801 #if FEATURE_COMINTEROP
802 if (!WindowsRuntimeMarshal.ReportUnhandledError(exc))
803 #endif // FEATURE_COMINTEROP
805 var edi = ExceptionDispatchInfo.Capture(exc);
806 ThreadPool.QueueUserWorkItem(s => ((ExceptionDispatchInfo)s).Throw(), edi);
811 internal override Delegate[] GetDelegateContinuationsForDebugger()
813 Debug.Assert(m_action != null);
814 return new Delegate[] { AsyncMethodBuilderCore.TryGetStateMachineForDebugger(m_action) };