42fc40e296b7dee4aee2e86209e726ab92798184
[platform/upstream/coreclr.git] / src / mscorlib / src / System / Threading / Tasks / TaskContinuation.cs
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.
4
5 // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
6 //
7 //
8 //
9 // Implementation of task continuations, TaskContinuation, and its descendants.
10 //
11 // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
12
13 using System.Security;
14 using System.Diagnostics;
15 using System.Runtime.ExceptionServices;
16 using System.Runtime.CompilerServices;
17 using System.Threading;
18
19 #if FEATURE_COMINTEROP
20 using System.Runtime.InteropServices.WindowsRuntime;
21 #endif // FEATURE_COMINTEROP
22
23 namespace System.Threading.Tasks
24 {
25     // Task type used to implement: Task ContinueWith(Action<Task,...>)
26     internal sealed class ContinuationTaskFromTask : Task
27     {
28         private Task m_antecedent;
29
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)
33         {
34             Debug.Assert(action is Action<Task> || action is Action<Task, object>,
35                 "Invalid delegate type in ContinuationTaskFromTask");
36             m_antecedent = antecedent;
37         }
38
39         /// <summary>
40         /// Evaluates the value selector of the Task which is passed in as an object and stores the result.
41         /// </summary>        
42         internal override void InnerInvoke()
43         {
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.");
49             m_antecedent = null;
50
51             // Notify the debugger we're completing an asynchronous wait on a task
52             antecedent.NotifyDebuggerOfWaitCompletionIfNecessary();
53
54             // Invoke the delegate
55             Debug.Assert(m_action != null);
56             var action = m_action as Action<Task>;
57             if (action != null)
58             {
59                 action(antecedent);
60                 return;
61             }
62             var actionWithState = m_action as Action<Task, object>;
63             if (actionWithState != null)
64             {
65                 actionWithState(antecedent, m_stateObject);
66                 return;
67             }
68             Debug.Fail("Invalid m_action in ContinuationTaskFromTask");
69         }
70     }
71
72     // Task type used to implement: Task<TResult> ContinueWith(Func<Task,...>)
73     internal sealed class ContinuationResultTaskFromTask<TResult> : Task<TResult>
74     {
75         private Task m_antecedent;
76
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)
80         {
81             Debug.Assert(function is Func<Task, TResult> || function is Func<Task, object, TResult>,
82                 "Invalid delegate type in ContinuationResultTaskFromTask");
83             m_antecedent = antecedent;
84         }
85
86         /// <summary>
87         /// Evaluates the value selector of the Task which is passed in as an object and stores the result.
88         /// </summary>        
89         internal override void InnerInvoke()
90         {
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.");
96             m_antecedent = null;
97
98             // Notify the debugger we're completing an asynchronous wait on a task
99             antecedent.NotifyDebuggerOfWaitCompletionIfNecessary();
100
101             // Invoke the delegate
102             Debug.Assert(m_action != null);
103             var func = m_action as Func<Task, TResult>;
104             if (func != null)
105             {
106                 m_result = func(antecedent);
107                 return;
108             }
109             var funcWithState = m_action as Func<Task, object, TResult>;
110             if (funcWithState != null)
111             {
112                 m_result = funcWithState(antecedent, m_stateObject);
113                 return;
114             }
115             Debug.Fail("Invalid m_action in ContinuationResultTaskFromTask");
116         }
117     }
118
119     // Task type used to implement: Task ContinueWith(Action<Task<TAntecedentResult>,...>)
120     internal sealed class ContinuationTaskFromResultTask<TAntecedentResult> : Task
121     {
122         private Task<TAntecedentResult> m_antecedent;
123
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)
127         {
128             Debug.Assert(action is Action<Task<TAntecedentResult>> || action is Action<Task<TAntecedentResult>, object>,
129                 "Invalid delegate type in ContinuationTaskFromResultTask");
130             m_antecedent = antecedent;
131         }
132
133         /// <summary>
134         /// Evaluates the value selector of the Task which is passed in as an object and stores the result.
135         /// </summary>
136         internal override void InnerInvoke()
137         {
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.");
143             m_antecedent = null;
144
145             // Notify the debugger we're completing an asynchronous wait on a task
146             antecedent.NotifyDebuggerOfWaitCompletionIfNecessary();
147
148             // Invoke the delegate
149             Debug.Assert(m_action != null);
150             var action = m_action as Action<Task<TAntecedentResult>>;
151             if (action != null)
152             {
153                 action(antecedent);
154                 return;
155             }
156             var actionWithState = m_action as Action<Task<TAntecedentResult>, object>;
157             if (actionWithState != null)
158             {
159                 actionWithState(antecedent, m_stateObject);
160                 return;
161             }
162             Debug.Fail("Invalid m_action in ContinuationTaskFromResultTask");
163         }
164     }
165
166     // Task type used to implement: Task<TResult> ContinueWith(Func<Task<TAntecedentResult>,...>)
167     internal sealed class ContinuationResultTaskFromResultTask<TAntecedentResult, TResult> : Task<TResult>
168     {
169         private Task<TAntecedentResult> m_antecedent;
170
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)
174         {
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;
178         }
179
180         /// <summary>
181         /// Evaluates the value selector of the Task which is passed in as an object and stores the result.
182         /// </summary>
183         internal override void InnerInvoke()
184         {
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.");
190             m_antecedent = null;
191
192             // Notify the debugger we're completing an asynchronous wait on a task
193             antecedent.NotifyDebuggerOfWaitCompletionIfNecessary();
194
195             // Invoke the delegate
196             Debug.Assert(m_action != null);
197             var func = m_action as Func<Task<TAntecedentResult>, TResult>;
198             if (func != null)
199             {
200                 m_result = func(antecedent);
201                 return;
202             }
203             var funcWithState = m_action as Func<Task<TAntecedentResult>, object, TResult>;
204             if (funcWithState != null)
205             {
206                 m_result = funcWithState(antecedent, m_stateObject);
207                 return;
208             }
209             Debug.Fail("Invalid m_action in ContinuationResultTaskFromResultTask");
210         }
211     }
212
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
220
221     /// <summary>Represents a continuation.</summary>
222     internal abstract class TaskContinuation
223     {
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);
228
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.
233         /// </param>
234         protected static void InlineIfPossibleOrElseQueue(Task task, bool needsProtection)
235         {
236             Debug.Assert(task != null);
237             Debug.Assert(task.m_taskScheduler != null);
238
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.
242             if (needsProtection)
243             {
244                 if (!task.MarkStarted())
245                     return; // task has been previously started or canceled.  Stop processing.
246             }
247             else
248             {
249                 task.m_stateFlags |= Task.TASK_STATE_STARTED;
250             }
251
252             // Try to inline it but queue if we can't
253             try
254             {
255                 if (!task.m_taskScheduler.TryRunInline(task, taskWasPreviouslyQueued: false))
256                 {
257                     task.m_taskScheduler.InternalQueueTask(task);
258                 }
259             }
260             catch (Exception e)
261             {
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
267                 {
268                     TaskSchedulerException tse = new TaskSchedulerException(e);
269                     task.AddException(tse);
270                     task.Finish(false);
271                 }
272
273                 // Don't re-throw.
274             }
275         }
276
277         internal abstract Delegate[] GetDelegateContinuationsForDebugger();
278     }
279
280     /// <summary>Provides the standard implementation of a task continuation.</summary>
281     internal class StandardTaskContinuation : TaskContinuation
282     {
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;
289
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)
295         {
296             Debug.Assert(task != null, "TaskContinuation ctor: task is null");
297             Debug.Assert(scheduler != null, "TaskContinuation ctor: scheduler is null");
298             m_task = task;
299             m_options = options;
300             m_taskScheduler = scheduler;
301             if (AsyncCausalityTracer.LoggingOn)
302                 AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, m_task.Id, "Task.ContinueWith: " + task.m_action.Method.Name, 0);
303
304             if (Task.s_asyncDebuggingEnabled)
305             {
306                 Task.AddToActiveTasks(m_task);
307             }
308         }
309
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)
314         {
315             Debug.Assert(completedTask != null);
316             Debug.Assert(completedTask.IsCompleted, "ContinuationTask.Run(): completedTask not completed");
317
318             // Check if the completion status of the task works with the desired 
319             // activation criteria of the TaskContinuationOptions.
320             TaskContinuationOptions options = m_options;
321             bool isRightKind =
322                 completedTask.IsCompletedSuccessfully ?
323                     (options & TaskContinuationOptions.NotOnRanToCompletion) == 0 :
324                     (completedTask.IsCanceled ?
325                         (options & TaskContinuationOptions.NotOnCanceled) == 0 :
326                         (options & TaskContinuationOptions.NotOnFaulted) == 0);
327
328             // If the completion status is allowed, run the continuation.
329             Task continuationTask = m_task;
330             if (isRightKind)
331             {
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)
336                 {
337                     // Log now that we are sure that this continuation is being ran
338                     AsyncCausalityTracer.TraceOperationRelation(CausalityTraceLevel.Important, continuationTask.Id, CausalityRelation.AssignDelegate);
339                 }
340                 continuationTask.m_taskScheduler = m_taskScheduler;
341
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
346                 {
347                     InlineIfPossibleOrElseQueue(continuationTask, needsProtection: true);
348                 }
349                 else
350                 {
351                     try { continuationTask.ScheduleAndStart(needsProtection: true); }
352                     catch (TaskSchedulerException)
353                     {
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.
356                     }
357                 }
358             }
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);
362         }
363
364         internal override Delegate[] GetDelegateContinuationsForDebugger()
365         {
366             if (m_task.m_action == null)
367             {
368                 return m_task.GetDelegateContinuationsForDebugger();
369             }
370
371             return new Delegate[] { m_task.m_action };
372         }
373     }
374
375     /// <summary>Task continuation for awaiting with a current synchronization context.</summary>
376     internal sealed class SynchronizationContextAwaitTaskContinuation : AwaitTaskContinuation
377     {
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;
384
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)
392         {
393             Debug.Assert(context != null);
394             m_syncContext = context;
395         }
396
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)
401         {
402             // If we're allowed to inline, run the action on this thread.
403             if (canInlineContinuationTask &&
404                 m_syncContext == SynchronizationContext.Current)
405             {
406                 RunCallback(GetInvokeActionCallback(), m_action, ref Task.t_currentTask);
407             }
408             // Otherwise, Post the action back to the SynchronizationContext.
409             else
410             {
411                 TplEtwProvider etwLog = TplEtwProvider.Log;
412                 if (etwLog.IsEnabled())
413                 {
414                     m_continuationId = Task.NewId();
415                     etwLog.AwaitTaskContinuationScheduled((task.ExecutingTaskScheduler ?? TaskScheduler.Default).Id, task.Id, m_continuationId);
416                 }
417                 RunCallback(GetPostActionCallback(), this, ref Task.t_currentTask);
418             }
419             // Any exceptions will be handled by RunCallback.
420         }
421
422         /// <summary>Calls InvokeOrPostAction(false) on the supplied SynchronizationContextAwaitTaskContinuation.</summary>
423         /// <param name="state">The SynchronizationContextAwaitTaskContinuation.</param>
424         private static void PostAction(object state)
425         {
426             var c = (SynchronizationContextAwaitTaskContinuation)state;
427
428             TplEtwProvider etwLog = TplEtwProvider.Log;
429             if (etwLog.TasksSetActivityIds && c.m_continuationId != 0)
430             {
431                 c.m_syncContext.Post(s_postCallback, GetActionLogDelegate(c.m_continuationId, c.m_action));
432             }
433             else
434             {
435                 c.m_syncContext.Post(s_postCallback, c.m_action); // s_postCallback is manually cached, as the compiler won't in a SecurityCritical method
436             }
437         }
438
439         private static Action GetActionLogDelegate(int continuationId, Action action)
440         {
441             return () =>
442                 {
443                     Guid savedActivityId;
444                     Guid activityId = TplEtwProvider.CreateGuidForTaskID(continuationId);
445                     System.Diagnostics.Tracing.EventSource.SetCurrentThreadActivityId(activityId, out savedActivityId);
446                     try { action(); }
447                     finally { System.Diagnostics.Tracing.EventSource.SetCurrentThreadActivityId(savedActivityId); }
448                 };
449         }
450
451         /// <summary>Gets a cached delegate for the PostAction method.</summary>
452         /// <returns>
453         /// A delegate for PostAction, which expects a SynchronizationContextAwaitTaskContinuation 
454         /// to be passed as state.
455         /// </returns>
456         [MethodImpl(MethodImplOptions.AggressiveInlining)]
457         private static ContextCallback GetPostActionCallback()
458         {
459             ContextCallback callback = s_postActionCallback;
460             if (callback == null) { s_postActionCallback = callback = PostAction; } // lazily initialize SecurityCritical delegate
461             return callback;
462         }
463     }
464
465     /// <summary>Task continuation for awaiting with a task scheduler.</summary>
466     internal sealed class TaskSchedulerAwaitTaskContinuation : AwaitTaskContinuation
467     {
468         /// <summary>The scheduler on which to run the action.</summary>
469         private readonly TaskScheduler m_scheduler;
470
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)
478         {
479             Debug.Assert(scheduler != null);
480             m_scheduler = scheduler;
481         }
482
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)
487         {
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)
490             {
491                 base.Run(ignored, canInlineContinuationTask);
492             }
493             else
494             {
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);
503
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 =>
507                 {
508                     try { ((Action)state)(); }
509                     catch (Exception exc) { ThrowAsyncIfNecessary(exc); }
510                 }, m_action, m_scheduler);
511
512                 if (inlineIfPossible)
513                 {
514                     InlineIfPossibleOrElseQueue(task, needsProtection: false);
515                 }
516                 else
517                 {
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
521                 }
522             }
523         }
524     }
525
526     /// <summary>Base task continuation class used for await continuations.</summary>
527     internal class AwaitTaskContinuation : TaskContinuation, IThreadPoolWorkItem
528     {
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;
533
534         protected int m_continuationId;
535
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)
540         {
541             Debug.Assert(action != null);
542             m_action = action;
543             if (flowExecutionContext)
544             {
545                 m_capturedContext = ExecutionContext.Capture();
546             }
547         }
548
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)
555         {
556             Debug.Assert(action != null);
557             Debug.Assert(scheduler != null);
558
559             return new Task(
560                 action, state, null, default(CancellationToken),
561                 TaskCreationOptions.None, InternalTaskOptions.QueuedByRuntime, scheduler)
562             {
563                 CapturedContext = m_capturedContext
564             };
565         }
566
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)
571         {
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)
578             {
579                 RunCallback(GetInvokeActionCallback(), m_action, ref Task.t_currentTask); // any exceptions from m_action will be handled by s_callbackRunAction
580             }
581             else
582             {
583                 TplEtwProvider etwLog = TplEtwProvider.Log;
584                 if (etwLog.IsEnabled())
585                 {
586                     m_continuationId = Task.NewId();
587                     etwLog.AwaitTaskContinuationScheduled((task.ExecutingTaskScheduler ?? TaskScheduler.Default).Id, task.Id, m_continuationId);
588                 }
589
590                 // We couldn't inline, so now we need to schedule it
591                 ThreadPool.UnsafeQueueCustomWorkItem(this, forceGlobal: false);
592             }
593         }
594
595         /// <summary>
596         /// Gets whether the current thread is an appropriate location to inline a continuation's execution.
597         /// </summary>
598         /// <remarks>
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.
610         /// </remarks>
611         internal static bool IsValidLocationForInlining
612         {
613             get
614             {
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;
619
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;
624             }
625         }
626
627         /// <summary>IThreadPoolWorkItem override, which is the entry function for this when the ThreadPool scheduler decides to run it.</summary>
628         private void ExecuteWorkItemHelper()
629         {
630             var etwLog = TplEtwProvider.Log;
631             Guid savedActivityId = Guid.Empty;
632             if (etwLog.TasksSetActivityIds && m_continuationId != 0)
633             {
634                 Guid activityId = TplEtwProvider.CreateGuidForTaskID(m_continuationId);
635                 System.Diagnostics.Tracing.EventSource.SetCurrentThreadActivityId(activityId, out savedActivityId);
636             }
637             try
638             {
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.
641
642                 // If there's no execution context, just invoke the delegate.
643                 if (m_capturedContext == null)
644                 {
645                     m_action();
646                 }
647                 // If there is an execution context, get the cached delegate and run the action under the context.
648                 else
649                 {
650                     ExecutionContext.Run(m_capturedContext, GetInvokeActionCallback(), m_action);
651                 }
652             }
653             finally
654             {
655                 if (etwLog.TasksSetActivityIds && m_continuationId != 0)
656                 {
657                     System.Diagnostics.Tracing.EventSource.SetCurrentThreadActivityId(savedActivityId);
658                 }
659             }
660         }
661
662         void IThreadPoolWorkItem.ExecuteWorkItem()
663         {
664             // inline the fast path
665             if (m_capturedContext == null && !TplEtwProvider.Log.IsEnabled())
666             {
667                 m_action();
668             }
669             else
670             {
671                 ExecuteWorkItemHelper();
672             }
673         }
674
675         /// <summary>
676         /// The ThreadPool calls this if a ThreadAbortException is thrown while trying to execute this workitem.
677         /// </summary>
678         void IThreadPoolWorkItem.MarkAborted(ThreadAbortException tae) { /* nop */ }
679
680         /// <summary>Cached delegate that invokes an Action passed as an object parameter.</summary>
681         private static ContextCallback s_invokeActionCallback;
682
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)(); }
686
687         [MethodImpl(MethodImplOptions.AggressiveInlining)]
688         protected static ContextCallback GetInvokeActionCallback()
689         {
690             ContextCallback callback = s_invokeActionCallback;
691             if (callback == null) { s_invokeActionCallback = callback = InvokeAction; } // lazily initialize SecurityCritical delegate
692             return callback;
693         }
694
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)
700         {
701             Debug.Assert(callback != null);
702             Debug.Assert(currentTask == Task.t_currentTask);
703
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;
707             try
708             {
709                 if (prevCurrentTask != null) currentTask = null;
710
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);
715             }
716             catch (Exception exc) // we explicitly do not request handling of dangerous exceptions like AVs
717             {
718                 ThrowAsyncIfNecessary(exc);
719             }
720             finally
721             {
722                 // Restore the current task information
723                 if (prevCurrentTask != null) currentTask = prevCurrentTask;
724             }
725         }
726
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.
731         /// </param>
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.
736         /// </param>
737         /// <remarks>
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.
742         /// </remarks>
743         internal static void RunOrScheduleAction(Action action, bool allowInlining, ref Task currentTask)
744         {
745             Debug.Assert(currentTask == Task.t_currentTask);
746
747             // If we're not allowed to run here, schedule the action
748             if (!allowInlining || !IsValidLocationForInlining)
749             {
750                 UnsafeScheduleAction(action, currentTask);
751                 return;
752             }
753
754             // Otherwise, run it, making sure that t_currentTask is null'd out appropriately during the execution
755             Task prevCurrentTask = currentTask;
756             try
757             {
758                 if (prevCurrentTask != null) currentTask = null;
759                 action();
760             }
761             catch (Exception exception)
762             {
763                 ThrowAsyncIfNecessary(exception);
764             }
765             finally
766             {
767                 if (prevCurrentTask != null) currentTask = prevCurrentTask;
768             }
769         }
770
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)
774         {
775             AwaitTaskContinuation atc = new AwaitTaskContinuation(action, flowExecutionContext: false);
776
777             var etwLog = TplEtwProvider.Log;
778             if (etwLog.IsEnabled() && task != null)
779             {
780                 atc.m_continuationId = Task.NewId();
781                 etwLog.AwaitTaskContinuationScheduled((task.ExecutingTaskScheduler ?? TaskScheduler.Default).Id, task.Id, atc.m_continuationId);
782             }
783
784             ThreadPool.UnsafeQueueCustomWorkItem(atc, forceGlobal: false);
785         }
786
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)
790         {
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.
795             //
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.
798
799             if (!(exc is ThreadAbortException || exc is AppDomainUnloadedException))
800             {
801 #if FEATURE_COMINTEROP
802                 if (!WindowsRuntimeMarshal.ReportUnhandledError(exc))
803 #endif // FEATURE_COMINTEROP
804                 {
805                     var edi = ExceptionDispatchInfo.Capture(exc);
806                     ThreadPool.QueueUserWorkItem(s => ((ExceptionDispatchInfo)s).Throw(), edi);
807                 }
808             }
809         }
810
811         internal override Delegate[] GetDelegateContinuationsForDebugger()
812         {
813             Debug.Assert(m_action != null);
814             return new Delegate[] { AsyncMethodBuilderCore.TryGetStateMachineForDebugger(m_action) };
815         }
816     }
817 }