Fix IDE0025 (use expression body for properties)
[mono-project.git] / netcore / System.Private.CoreLib / shared / System / Threading / Tasks / Task.cs
blobef746396e8b147b87d0aee57c8f95e7f13fd323b
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 // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
6 //
7 //
8 //
9 // A schedulable unit of work.
11 // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
13 using System.Collections.Generic;
14 using System.Collections.ObjectModel;
15 using System.Diagnostics;
16 using System.Diagnostics.Tracing;
17 using System.Runtime.CompilerServices;
18 using System.Runtime.ExceptionServices;
19 using Internal.Runtime.CompilerServices;
21 namespace System.Threading.Tasks
23 /// <summary>
24 /// Represents the current stage in the lifecycle of a <see cref="Task"/>.
25 /// </summary>
26 public enum TaskStatus
28 /// <summary>
29 /// The task has been initialized but has not yet been scheduled.
30 /// </summary>
31 Created,
32 /// <summary>
33 /// The task is waiting to be activated and scheduled internally by the .NET Framework infrastructure.
34 /// </summary>
35 WaitingForActivation,
36 /// <summary>
37 /// The task has been scheduled for execution but has not yet begun executing.
38 /// </summary>
39 WaitingToRun,
40 /// <summary>
41 /// The task is running but has not yet completed.
42 /// </summary>
43 Running,
44 // /// <summary>
45 // /// The task is currently blocked in a wait state.
46 // /// </summary>
47 // Blocked,
48 /// <summary>
49 /// The task has finished executing and is implicitly waiting for
50 /// attached child tasks to complete.
51 /// </summary>
52 WaitingForChildrenToComplete,
53 /// <summary>
54 /// The task completed execution successfully.
55 /// </summary>
56 RanToCompletion,
57 /// <summary>
58 /// The task acknowledged cancellation by throwing an OperationCanceledException with its own CancellationToken
59 /// while the token was in signaled state, or the task's CancellationToken was already signaled before the
60 /// task started executing.
61 /// </summary>
62 Canceled,
63 /// <summary>
64 /// The task completed due to an unhandled exception.
65 /// </summary>
66 Faulted
69 /// <summary>
70 /// Represents an asynchronous operation.
71 /// </summary>
72 /// <remarks>
73 /// <para>
74 /// <see cref="Task"/> instances may be created in a variety of ways. The most common approach is by
75 /// using the Task type's <see cref="Factory"/> property to retrieve a <see
76 /// cref="System.Threading.Tasks.TaskFactory"/> instance that can be used to create tasks for several
77 /// purposes. For example, to create a <see cref="Task"/> that runs an action, the factory's StartNew
78 /// method may be used:
79 /// <code>
80 /// // C#
81 /// var t = Task.Factory.StartNew(() => DoAction());
82 ///
83 /// ' Visual Basic
84 /// Dim t = Task.Factory.StartNew(Function() DoAction())
85 /// </code>
86 /// </para>
87 /// <para>
88 /// The <see cref="Task"/> class also provides constructors that initialize the Task but that do not
89 /// schedule it for execution. For performance reasons, TaskFactory's StartNew method should be the
90 /// preferred mechanism for creating and scheduling computational tasks, but for scenarios where creation
91 /// and scheduling must be separated, the constructors may be used, and the task's <see cref="Start()"/>
92 /// method may then be used to schedule the task for execution at a later time.
93 /// </para>
94 /// <para>
95 /// All members of <see cref="Task"/>, except for <see cref="Dispose()"/>, are thread-safe
96 /// and may be used from multiple threads concurrently.
97 /// </para>
98 /// <para>
99 /// For operations that return values, the <see cref="System.Threading.Tasks.Task{TResult}"/> class
100 /// should be used.
101 /// </para>
102 /// <para>
103 /// For developers implementing custom debuggers, several internal and private members of Task may be
104 /// useful (these may change from release to release). The Int32 m_taskId field serves as the backing
105 /// store for the <see cref="Id"/> property, however accessing this field directly from a debugger may be
106 /// more efficient than accessing the same value through the property's getter method (the
107 /// s_taskIdCounter Int32 counter is used to retrieve the next available ID for a Task). Similarly, the
108 /// Int32 m_stateFlags field stores information about the current lifecycle stage of the Task,
109 /// information also accessible through the <see cref="Status"/> property. The m_action System.Object
110 /// field stores a reference to the Task's delegate, and the m_stateObject System.Object field stores the
111 /// async state passed to the Task by the developer. Finally, for debuggers that parse stack frames, the
112 /// InternalWait method serves a potential marker for when a Task is entering a wait operation.
113 /// </para>
114 /// </remarks>
115 [DebuggerTypeProxy(typeof(SystemThreadingTasks_TaskDebugView))]
116 [DebuggerDisplay("Id = {Id}, Status = {Status}, Method = {DebuggerDisplayMethodDescription}")]
117 public class Task : IAsyncResult, IDisposable
119 [ThreadStatic]
120 internal static Task? t_currentTask; // The currently executing task.
122 internal static int s_taskIdCounter; //static counter used to generate unique task IDs
124 private volatile int m_taskId; // this task's unique ID. initialized only if it is ever requested
126 internal Delegate? m_action; // The body of the task. Might be Action<object>, Action<TState> or Action. Or possibly a Func.
127 // If m_action is set to null it will indicate that we operate in the
128 // "externally triggered completion" mode, which is exclusively meant
129 // for the signalling Task<TResult> (aka. promise). In this mode,
130 // we don't call InnerInvoke() in response to a Wait(), but simply wait on
131 // the completion event which will be set when the Future class calls Finish().
132 // But the event would now be signalled if Cancel() is called
135 internal object? m_stateObject; // A state object that can be optionally supplied, passed to action.
136 internal TaskScheduler? m_taskScheduler; // The task scheduler this task runs under.
138 internal volatile int m_stateFlags; // SOS DumpAsync command depends on this name
140 private Task? ParentForDebugger => m_contingentProperties?.m_parent; // Private property used by a debugger to access this Task's parent
141 private int StateFlagsForDebugger => m_stateFlags; // Private property used by a debugger to access this Task's state flags
143 // State constants for m_stateFlags;
144 // The bits of m_stateFlags are allocated as follows:
145 // 0x40000000 - TaskBase state flag
146 // 0x3FFF0000 - Task state flags
147 // 0x0000FF00 - internal TaskCreationOptions flags
148 // 0x000000FF - publicly exposed TaskCreationOptions flags
150 // See TaskCreationOptions for bit values associated with TaskCreationOptions
152 private const int OptionsMask = 0xFFFF; // signifies the Options portion of m_stateFlags bin: 0000 0000 0000 0000 1111 1111 1111 1111
153 internal const int TASK_STATE_STARTED = 0x10000; //bin: 0000 0000 0000 0001 0000 0000 0000 0000
154 internal const int TASK_STATE_DELEGATE_INVOKED = 0x20000; //bin: 0000 0000 0000 0010 0000 0000 0000 0000
155 internal const int TASK_STATE_DISPOSED = 0x40000; //bin: 0000 0000 0000 0100 0000 0000 0000 0000
156 internal const int TASK_STATE_EXCEPTIONOBSERVEDBYPARENT = 0x80000; //bin: 0000 0000 0000 1000 0000 0000 0000 0000
157 internal const int TASK_STATE_CANCELLATIONACKNOWLEDGED = 0x100000; //bin: 0000 0000 0001 0000 0000 0000 0000 0000
158 internal const int TASK_STATE_FAULTED = 0x200000; //bin: 0000 0000 0010 0000 0000 0000 0000 0000
159 internal const int TASK_STATE_CANCELED = 0x400000; //bin: 0000 0000 0100 0000 0000 0000 0000 0000
160 internal const int TASK_STATE_WAITING_ON_CHILDREN = 0x800000; //bin: 0000 0000 1000 0000 0000 0000 0000 0000
161 internal const int TASK_STATE_RAN_TO_COMPLETION = 0x1000000; //bin: 0000 0001 0000 0000 0000 0000 0000 0000
162 internal const int TASK_STATE_WAITINGFORACTIVATION = 0x2000000; //bin: 0000 0010 0000 0000 0000 0000 0000 0000
163 internal const int TASK_STATE_COMPLETION_RESERVED = 0x4000000; //bin: 0000 0100 0000 0000 0000 0000 0000 0000
164 internal const int TASK_STATE_WAIT_COMPLETION_NOTIFICATION = 0x10000000; //bin: 0001 0000 0000 0000 0000 0000 0000 0000
165 //This could be moved to InternalTaskOptions enum
166 internal const int TASK_STATE_EXECUTIONCONTEXT_IS_NULL = 0x20000000; //bin: 0010 0000 0000 0000 0000 0000 0000 0000
167 internal const int TASK_STATE_TASKSCHEDULED_WAS_FIRED = 0x40000000; //bin: 0100 0000 0000 0000 0000 0000 0000 0000
169 // A mask for all of the final states a task may be in.
170 // SOS DumpAsync command depends on these values.
171 private const int TASK_STATE_COMPLETED_MASK = TASK_STATE_CANCELED | TASK_STATE_FAULTED | TASK_STATE_RAN_TO_COMPLETION;
173 // Values for ContingentProperties.m_internalCancellationRequested.
174 private const int CANCELLATION_REQUESTED = 0x1;
176 // Can be null, a single continuation, a list of continuations, or s_taskCompletionSentinel,
177 // in that order. The logic arround this object assumes it will never regress to a previous state.
178 private volatile object? m_continuationObject = null; // SOS DumpAsync command depends on this name
180 // m_continuationObject is set to this when the task completes.
181 private static readonly object s_taskCompletionSentinel = new object();
183 // A private flag that would be set (only) by the debugger
184 // When true the Async Causality logging trace is enabled as well as a dictionary to relate operation ids with Tasks
185 internal static bool s_asyncDebuggingEnabled; //false by default
187 // This dictonary relates the task id, from an operation id located in the Async Causality log to the actual
188 // task. This is to be used by the debugger ONLY. Task in this dictionary represent current active tasks.
189 private static Dictionary<int, Task>? s_currentActiveTasks;
191 // These methods are a way to access the dictionary both from this class and for other classes that also
192 // activate dummy tasks. Specifically the AsyncTaskMethodBuilder and AsyncTaskMethodBuilder<>
193 internal static bool AddToActiveTasks(Task task)
195 Debug.Assert(task != null, "Null Task objects can't be added to the ActiveTasks collection");
197 LazyInitializer.EnsureInitialized(ref s_currentActiveTasks, () => new Dictionary<int, Task>());
199 int taskId = task.Id;
200 lock (s_currentActiveTasks)
202 s_currentActiveTasks[taskId] = task;
204 //always return true to keep signature as bool for backwards compatibility
205 return true;
208 internal static void RemoveFromActiveTasks(Task task)
210 if (s_currentActiveTasks == null)
211 return;
213 int taskId = task.Id;
214 lock (s_currentActiveTasks)
216 s_currentActiveTasks.Remove(taskId);
220 // We moved a number of Task properties into this class. The idea is that in most cases, these properties never
221 // need to be accessed during the life cycle of a Task, so we don't want to instantiate them every time. Once
222 // one of these properties needs to be written, we will instantiate a ContingentProperties object and set
223 // the appropriate property.
224 internal class ContingentProperties
226 // Additional context
228 internal ExecutionContext? m_capturedContext; // The execution context to run the task within, if any. Only set from non-concurrent contexts.
230 // Completion fields (exceptions and event)
232 internal volatile ManualResetEventSlim? m_completionEvent; // Lazily created if waiting is required.
233 internal volatile TaskExceptionHolder? m_exceptionsHolder; // Tracks exceptions, if any have occurred
235 // Cancellation fields (token, registration, and internally requested)
237 internal CancellationToken m_cancellationToken; // Task's cancellation token, if it has one
238 internal StrongBox<CancellationTokenRegistration>? m_cancellationRegistration; // Task's registration with the cancellation token
239 internal volatile int m_internalCancellationRequested; // Its own field because multiple threads legally try to set it.
241 // Parenting fields
243 // # of active children + 1 (for this task itself).
244 // Used for ensuring all children are done before this task can complete
245 // The extra count helps prevent the race condition for executing the final state transition
246 // (i.e. whether the last child or this task itself should call FinishStageTwo())
247 internal volatile int m_completionCountdown = 1;
248 // A list of child tasks that threw an exception (TCEs don't count),
249 // but haven't yet been waited on by the parent, lazily initialized.
250 internal volatile List<Task>? m_exceptionalChildren;
251 // A task's parent, or null if parent-less. Only set during Task construction.
252 internal Task? m_parent;
254 /// <summary>
255 /// Sets the internal completion event.
256 /// </summary>
257 internal void SetCompleted()
259 ManualResetEventSlim? mres = m_completionEvent;
260 if (mres != null) mres.Set();
263 /// <summary>
264 /// Checks if we registered a CT callback during construction, and unregisters it.
265 /// This should be called when we know the registration isn't useful anymore. Specifically from Finish() if the task has completed
266 /// successfully or with an exception.
267 /// </summary>
268 internal void UnregisterCancellationCallback()
270 if (m_cancellationRegistration != null)
272 // Harden against ODEs thrown from disposing of the CTR.
273 // Since the task has already been put into a final state by the time this
274 // is called, all we can do here is suppress the exception.
275 try { m_cancellationRegistration.Value.Dispose(); }
276 catch (ObjectDisposedException) { }
277 m_cancellationRegistration = null;
283 // This field will only be instantiated to some non-null value if any ContingentProperties need to be set.
284 // This will be a ContingentProperties instance or a type derived from it
285 internal ContingentProperties? m_contingentProperties;
287 // Special internal constructor to create an already-completed task.
288 // if canceled==true, create a Canceled task, or else create a RanToCompletion task.
289 // Constructs the task as already completed
290 internal Task(bool canceled, TaskCreationOptions creationOptions, CancellationToken ct)
292 int optionFlags = (int)creationOptions;
293 if (canceled)
295 m_stateFlags = TASK_STATE_CANCELED | TASK_STATE_CANCELLATIONACKNOWLEDGED | optionFlags;
296 m_contingentProperties = new ContingentProperties() // can't have children, so just instantiate directly
298 m_cancellationToken = ct,
299 m_internalCancellationRequested = CANCELLATION_REQUESTED,
302 else
303 m_stateFlags = TASK_STATE_RAN_TO_COMPLETION | optionFlags;
306 /// <summary>Constructor for use with promise-style tasks that aren't configurable.</summary>
307 internal Task()
309 m_stateFlags = TASK_STATE_WAITINGFORACTIVATION | (int)InternalTaskOptions.PromiseTask;
312 // Special constructor for use with promise-style tasks.
313 // Added promiseStyle parameter as an aid to the compiler to distinguish between (state,TCO) and
314 // (action,TCO). It should always be true.
315 internal Task(object? state, TaskCreationOptions creationOptions, bool promiseStyle)
317 Debug.Assert(promiseStyle, "Promise CTOR: promiseStyle was false");
319 // Check the creationOptions. We allow the AttachedToParent option to be specified for promise tasks.
320 // Also allow RunContinuationsAsynchronously because this is the constructor called by TCS
321 if ((creationOptions & ~(TaskCreationOptions.AttachedToParent | TaskCreationOptions.RunContinuationsAsynchronously)) != 0)
323 ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.creationOptions);
326 // Only set a parent if AttachedToParent is specified.
327 if ((creationOptions & TaskCreationOptions.AttachedToParent) != 0)
329 Task? parent = Task.InternalCurrent;
330 if (parent != null)
332 EnsureContingentPropertiesInitializedUnsafe().m_parent = parent;
336 TaskConstructorCore(null, state, default, creationOptions, InternalTaskOptions.PromiseTask, null);
339 /// <summary>
340 /// Initializes a new <see cref="Task"/> with the specified action.
341 /// </summary>
342 /// <param name="action">The delegate that represents the code to execute in the Task.</param>
343 /// <exception cref="System.ArgumentNullException">The <paramref name="action"/> argument is null.</exception>
344 public Task(Action action)
345 : this(action, null, null, default, TaskCreationOptions.None, InternalTaskOptions.None, null)
349 /// <summary>
350 /// Initializes a new <see cref="Task"/> with the specified action and <see cref="System.Threading.CancellationToken">CancellationToken</see>.
351 /// </summary>
352 /// <param name="action">The delegate that represents the code to execute in the Task.</param>
353 /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken">CancellationToken</see>
354 /// that will be assigned to the new Task.</param>
355 /// <exception cref="System.ArgumentNullException">The <paramref name="action"/> argument is null.</exception>
356 /// <exception cref="System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see>
357 /// has already been disposed.
358 /// </exception>
359 public Task(Action action, CancellationToken cancellationToken)
360 : this(action, null, null, cancellationToken, TaskCreationOptions.None, InternalTaskOptions.None, null)
364 /// <summary>
365 /// Initializes a new <see cref="Task"/> with the specified action and creation options.
366 /// </summary>
367 /// <param name="action">The delegate that represents the code to execute in the task.</param>
368 /// <param name="creationOptions">
369 /// The <see cref="System.Threading.Tasks.TaskCreationOptions">TaskCreationOptions</see> used to
370 /// customize the Task's behavior.
371 /// </param>
372 /// <exception cref="System.ArgumentNullException">
373 /// The <paramref name="action"/> argument is null.
374 /// </exception>
375 /// <exception cref="System.ArgumentOutOfRangeException">
376 /// The <paramref name="creationOptions"/> argument specifies an invalid value for <see
377 /// cref="System.Threading.Tasks.TaskCreationOptions"/>.
378 /// </exception>
379 public Task(Action action, TaskCreationOptions creationOptions)
380 : this(action, null, Task.InternalCurrentIfAttached(creationOptions), default, creationOptions, InternalTaskOptions.None, null)
384 /// <summary>
385 /// Initializes a new <see cref="Task"/> with the specified action and creation options.
386 /// </summary>
387 /// <param name="action">The delegate that represents the code to execute in the task.</param>
388 /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new task.</param>
389 /// <param name="creationOptions">
390 /// The <see cref="System.Threading.Tasks.TaskCreationOptions">TaskCreationOptions</see> used to
391 /// customize the Task's behavior.
392 /// </param>
393 /// <exception cref="System.ArgumentNullException">
394 /// The <paramref name="action"/> argument is null.
395 /// </exception>
396 /// <exception cref="System.ArgumentOutOfRangeException">
397 /// The <paramref name="creationOptions"/> argument specifies an invalid value for <see
398 /// cref="System.Threading.Tasks.TaskCreationOptions"/>.
399 /// </exception>
400 /// <exception cref="System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see>
401 /// has already been disposed.
402 /// </exception>
403 public Task(Action action, CancellationToken cancellationToken, TaskCreationOptions creationOptions)
404 : this(action, null, Task.InternalCurrentIfAttached(creationOptions), cancellationToken, creationOptions, InternalTaskOptions.None, null)
409 /// <summary>
410 /// Initializes a new <see cref="Task"/> with the specified action and state.
411 /// </summary>
412 /// <param name="action">The delegate that represents the code to execute in the task.</param>
413 /// <param name="state">An object representing data to be used by the action.</param>
414 /// <exception cref="System.ArgumentNullException">
415 /// The <paramref name="action"/> argument is null.
416 /// </exception>
417 public Task(Action<object?> action, object? state)
418 : this(action, state, null, default, TaskCreationOptions.None, InternalTaskOptions.None, null)
422 /// <summary>
423 /// Initializes a new <see cref="Task"/> with the specified action, state, and options.
424 /// </summary>
425 /// <param name="action">The delegate that represents the code to execute in the task.</param>
426 /// <param name="state">An object representing data to be used by the action.</param>
427 /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new task.</param>
428 /// <exception cref="System.ArgumentNullException">
429 /// The <paramref name="action"/> argument is null.
430 /// </exception>
431 /// <exception cref="System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see>
432 /// has already been disposed.
433 /// </exception>
434 public Task(Action<object?> action, object? state, CancellationToken cancellationToken)
435 : this(action, state, null, cancellationToken, TaskCreationOptions.None, InternalTaskOptions.None, null)
439 /// <summary>
440 /// Initializes a new <see cref="Task"/> with the specified action, state, and options.
441 /// </summary>
442 /// <param name="action">The delegate that represents the code to execute in the task.</param>
443 /// <param name="state">An object representing data to be used by the action.</param>
444 /// <param name="creationOptions">
445 /// The <see cref="System.Threading.Tasks.TaskCreationOptions">TaskCreationOptions</see> used to
446 /// customize the Task's behavior.
447 /// </param>
448 /// <exception cref="System.ArgumentNullException">
449 /// The <paramref name="action"/> argument is null.
450 /// </exception>
451 /// <exception cref="System.ArgumentOutOfRangeException">
452 /// The <paramref name="creationOptions"/> argument specifies an invalid value for <see
453 /// cref="System.Threading.Tasks.TaskCreationOptions"/>.
454 /// </exception>
455 public Task(Action<object?> action, object? state, TaskCreationOptions creationOptions)
456 : this(action, state, Task.InternalCurrentIfAttached(creationOptions), default, creationOptions, InternalTaskOptions.None, null)
460 /// <summary>
461 /// Initializes a new <see cref="Task"/> with the specified action, state, and options.
462 /// </summary>
463 /// <param name="action">The delegate that represents the code to execute in the task.</param>
464 /// <param name="state">An object representing data to be used by the action.</param>
465 /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new task.</param>
466 /// <param name="creationOptions">
467 /// The <see cref="System.Threading.Tasks.TaskCreationOptions">TaskCreationOptions</see> used to
468 /// customize the Task's behavior.
469 /// </param>
470 /// <exception cref="System.ArgumentNullException">
471 /// The <paramref name="action"/> argument is null.
472 /// </exception>
473 /// <exception cref="System.ArgumentOutOfRangeException">
474 /// The <paramref name="creationOptions"/> argument specifies an invalid value for <see
475 /// cref="System.Threading.Tasks.TaskCreationOptions"/>.
476 /// </exception>
477 /// <exception cref="System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see>
478 /// has already been disposed.
479 /// </exception>
480 public Task(Action<object?> action, object? state, CancellationToken cancellationToken, TaskCreationOptions creationOptions)
481 : this(action, state, Task.InternalCurrentIfAttached(creationOptions), cancellationToken, creationOptions, InternalTaskOptions.None, null)
485 /// <summary>
486 /// An internal constructor used by the factory methods on task and its descendent(s).
487 /// </summary>
488 /// <param name="action">An action to execute.</param>
489 /// <param name="state">Optional state to pass to the action.</param>
490 /// <param name="parent">Parent of Task.</param>
491 /// <param name="cancellationToken">A CancellationToken for the task.</param>
492 /// <param name="scheduler">A task scheduler under which the task will run.</param>
493 /// <param name="creationOptions">Options to control its execution.</param>
494 /// <param name="internalOptions">Internal options to control its execution</param>
495 internal Task(Delegate action, object? state, Task? parent, CancellationToken cancellationToken,
496 TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, TaskScheduler? scheduler)
498 if (action == null)
500 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.action);
503 // Keep a link to the parent if attached
504 if (parent != null && (creationOptions & TaskCreationOptions.AttachedToParent) != 0)
506 EnsureContingentPropertiesInitializedUnsafe().m_parent = parent;
509 TaskConstructorCore(action, state, cancellationToken, creationOptions, internalOptions, scheduler);
511 Debug.Assert(m_contingentProperties == null || m_contingentProperties.m_capturedContext == null,
512 "Captured an ExecutionContext when one was already captured.");
513 CapturedContext = ExecutionContext.Capture();
516 /// <summary>
517 /// Common logic used by the following internal ctors:
518 /// Task()
519 /// Task(object action, object state, Task parent, TaskCreationOptions options, TaskScheduler taskScheduler)
520 /// </summary>
521 /// <param name="action">Action for task to execute.</param>
522 /// <param name="state">Object to which to pass to action (may be null)</param>
523 /// <param name="scheduler">Task scheduler on which to run thread (only used by continuation tasks).</param>
524 /// <param name="cancellationToken">A CancellationToken for the Task.</param>
525 /// <param name="creationOptions">Options to customize behavior of Task.</param>
526 /// <param name="internalOptions">Internal options to customize behavior of Task.</param>
527 internal void TaskConstructorCore(Delegate? action, object? state, CancellationToken cancellationToken,
528 TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, TaskScheduler? scheduler)
530 m_action = action;
531 m_stateObject = state;
532 m_taskScheduler = scheduler;
534 // Check for validity of options
535 if ((creationOptions &
536 ~(TaskCreationOptions.AttachedToParent |
537 TaskCreationOptions.LongRunning |
538 TaskCreationOptions.DenyChildAttach |
539 TaskCreationOptions.HideScheduler |
540 TaskCreationOptions.PreferFairness |
541 TaskCreationOptions.RunContinuationsAsynchronously)) != 0)
543 ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.creationOptions);
546 #if DEBUG
547 // Check the validity of internalOptions
548 int illegalInternalOptions =
549 (int)(internalOptions &
550 ~(InternalTaskOptions.PromiseTask |
551 InternalTaskOptions.ContinuationTask |
552 InternalTaskOptions.LazyCancellation |
553 InternalTaskOptions.QueuedByRuntime));
554 Debug.Assert(illegalInternalOptions == 0, "TaskConstructorCore: Illegal internal options");
555 #endif
557 // Assign options to m_stateAndOptionsFlag.
558 Debug.Assert(m_stateFlags == 0, "TaskConstructorCore: non-zero m_stateFlags");
559 Debug.Assert((((int)creationOptions) | OptionsMask) == OptionsMask, "TaskConstructorCore: options take too many bits");
560 int tmpFlags = (int)creationOptions | (int)internalOptions; // one write to the volatile m_stateFlags instead of two when setting the above options
561 m_stateFlags = m_action == null || (internalOptions & InternalTaskOptions.ContinuationTask) != 0 ?
562 tmpFlags | TASK_STATE_WAITINGFORACTIVATION :
563 tmpFlags;
565 // Now is the time to add the new task to the children list
566 // of the creating task if the options call for it.
567 // We can safely call the creator task's AddNewChild() method to register it,
568 // because at this point we are already on its thread of execution.
570 ContingentProperties? props = m_contingentProperties;
571 if (props != null)
573 Task? parent = props.m_parent;
574 if (parent != null
575 && ((creationOptions & TaskCreationOptions.AttachedToParent) != 0)
576 && ((parent.CreationOptions & TaskCreationOptions.DenyChildAttach) == 0))
578 parent.AddNewChild();
582 // if we have a non-null cancellationToken, allocate the contingent properties to save it
583 // we need to do this as the very last thing in the construction path, because the CT registration could modify m_stateFlags
584 if (cancellationToken.CanBeCanceled)
586 Debug.Assert((internalOptions & InternalTaskOptions.ContinuationTask) == 0, "TaskConstructorCore: Did not expect to see cancelable token for continuation task.");
588 AssignCancellationToken(cancellationToken, null, null);
592 /// <summary>
593 /// Handles everything needed for associating a CancellationToken with a task which is being constructed.
594 /// This method is meant to be called either from the TaskConstructorCore or from ContinueWithCore.
595 /// </summary>
596 private void AssignCancellationToken(CancellationToken cancellationToken, Task? antecedent, TaskContinuation? continuation)
598 // There is no need to worry about concurrency issues here because we are in the constructor path of the task --
599 // there should not be any race conditions to set m_contingentProperties at this point.
600 ContingentProperties props = EnsureContingentPropertiesInitializedUnsafe();
601 props.m_cancellationToken = cancellationToken;
605 // If an unstarted task has a valid CancellationToken that gets signalled while the task is still not queued
606 // we need to proactively cancel it, because it may never execute to transition itself.
607 // The only way to accomplish this is to register a callback on the CT.
608 // We exclude Promise tasks from this, because TaskCompletionSource needs to fully control the inner tasks's lifetime (i.e. not allow external cancellations)
609 if ((((InternalTaskOptions)Options &
610 (InternalTaskOptions.QueuedByRuntime | InternalTaskOptions.PromiseTask | InternalTaskOptions.LazyCancellation)) == 0))
612 if (cancellationToken.IsCancellationRequested)
614 // Fast path for an already-canceled cancellationToken
615 this.InternalCancel(false);
617 else
619 // Regular path for an uncanceled cancellationToken
620 CancellationTokenRegistration ctr;
621 if (antecedent == null)
623 // if no antecedent was specified, use this task's reference as the cancellation state object
624 ctr = cancellationToken.UnsafeRegister(t => ((Task)t!).InternalCancel(false), this);
626 else
628 Debug.Assert(continuation != null);
630 // If an antecedent was specified, pack this task, its antecedent and the TaskContinuation together as a tuple
631 // and use it as the cancellation state object. This will be unpacked in the cancellation callback so that
632 // antecedent.RemoveCancellation(continuation) can be invoked.
633 ctr = cancellationToken.UnsafeRegister(t =>
635 var tuple = (Tuple<Task, Task, TaskContinuation>)t!;
637 Task targetTask = tuple.Item1;
638 Task antecedentTask = tuple.Item2;
640 antecedentTask.RemoveContinuation(tuple.Item3);
641 targetTask.InternalCancel(false);
643 new Tuple<Task, Task, TaskContinuation>(this, antecedent, continuation));
646 props.m_cancellationRegistration = new StrongBox<CancellationTokenRegistration>(ctr);
650 catch
652 // If we have an exception related to our CancellationToken, then we need to subtract ourselves
653 // from our parent before throwing it.
654 Task? parent = m_contingentProperties?.m_parent;
655 if ((parent != null) &&
656 ((Options & TaskCreationOptions.AttachedToParent) != 0)
657 && ((parent.Options & TaskCreationOptions.DenyChildAttach) == 0))
659 parent.DisregardChild();
661 throw;
665 // Debugger support
666 private string DebuggerDisplayMethodDescription => m_action?.Method.ToString() ?? "{null}";
668 // Internal property to process TaskCreationOptions access and mutation.
669 internal TaskCreationOptions Options => OptionsMethod(m_stateFlags);
671 // Similar to Options property, but allows for the use of a cached flags value rather than
672 // a read of the volatile m_stateFlags field.
673 internal static TaskCreationOptions OptionsMethod(int flags)
675 Debug.Assert((OptionsMask & 1) == 1, "OptionsMask needs a shift in Options.get");
676 return (TaskCreationOptions)(flags & OptionsMask);
679 // Atomically OR-in newBits to m_stateFlags, while making sure that
680 // no illegalBits are set. Returns true on success, false on failure.
681 internal bool AtomicStateUpdate(int newBits, int illegalBits)
683 int oldFlags = m_stateFlags;
684 return
685 (oldFlags & illegalBits) == 0 &&
686 (Interlocked.CompareExchange(ref m_stateFlags, oldFlags | newBits, oldFlags) == oldFlags ||
687 AtomicStateUpdateSlow(newBits, illegalBits));
690 private bool AtomicStateUpdateSlow(int newBits, int illegalBits)
692 int flags = m_stateFlags;
695 if ((flags & illegalBits) != 0) return false;
696 int oldFlags = Interlocked.CompareExchange(ref m_stateFlags, flags | newBits, flags);
697 if (oldFlags == flags)
699 return true;
701 flags = oldFlags;
702 } while (true);
705 internal bool AtomicStateUpdate(int newBits, int illegalBits, ref int oldFlags)
707 int flags = oldFlags = m_stateFlags;
710 if ((flags & illegalBits) != 0) return false;
711 oldFlags = Interlocked.CompareExchange(ref m_stateFlags, flags | newBits, flags);
712 if (oldFlags == flags)
714 return true;
716 flags = oldFlags;
717 } while (true);
720 /// <summary>
721 /// Sets or clears the TASK_STATE_WAIT_COMPLETION_NOTIFICATION state bit.
722 /// The debugger sets this bit to aid it in "stepping out" of an async method body.
723 /// If enabled is true, this must only be called on a task that has not yet been completed.
724 /// If enabled is false, this may be called on completed tasks.
725 /// Either way, it should only be used for promise-style tasks.
726 /// </summary>
727 /// <param name="enabled">true to set the bit; false to unset the bit.</param>
728 internal void SetNotificationForWaitCompletion(bool enabled)
730 Debug.Assert((Options & (TaskCreationOptions)InternalTaskOptions.PromiseTask) != 0,
731 "Should only be used for promise-style tasks"); // hasn't been vetted on other kinds as there hasn't been a need
733 if (enabled)
735 // Atomically set the END_AWAIT_NOTIFICATION bit
736 bool success = AtomicStateUpdate(TASK_STATE_WAIT_COMPLETION_NOTIFICATION,
737 TASK_STATE_COMPLETED_MASK | TASK_STATE_COMPLETION_RESERVED);
738 Debug.Assert(success, "Tried to set enabled on completed Task");
740 else
742 // Atomically clear the END_AWAIT_NOTIFICATION bit
743 int flags = m_stateFlags;
744 while (true)
746 int oldFlags = Interlocked.CompareExchange(ref m_stateFlags, flags & (~TASK_STATE_WAIT_COMPLETION_NOTIFICATION), flags);
747 if (oldFlags == flags) break;
748 flags = oldFlags;
753 /// <summary>
754 /// Calls the debugger notification method if the right bit is set and if
755 /// the task itself allows for the notification to proceed.
756 /// </summary>
757 /// <returns>true if the debugger was notified; otherwise, false.</returns>
758 internal bool NotifyDebuggerOfWaitCompletionIfNecessary()
760 // Notify the debugger if of any of the tasks we've waited on requires notification
761 if (IsWaitNotificationEnabled && ShouldNotifyDebuggerOfWaitCompletion)
763 NotifyDebuggerOfWaitCompletion();
764 return true;
766 return false;
769 /// <summary>Returns true if any of the supplied tasks require wait notification.</summary>
770 /// <param name="tasks">The tasks to check.</param>
771 /// <returns>true if any of the tasks require notification; otherwise, false.</returns>
772 internal static bool AnyTaskRequiresNotifyDebuggerOfWaitCompletion(Task?[] tasks)
774 Debug.Assert(tasks != null, "Expected non-null array of tasks");
775 foreach (Task? task in tasks)
777 if (task != null &&
778 task.IsWaitNotificationEnabled &&
779 task.ShouldNotifyDebuggerOfWaitCompletion) // potential recursion
781 return true;
784 return false;
787 /// <summary>Gets whether either the end await bit is set or (not xor) the task has not completed successfully.</summary>
788 /// <returns>(DebuggerBitSet || !RanToCompletion)</returns>
789 internal bool IsWaitNotificationEnabledOrNotRanToCompletion
791 [MethodImpl(MethodImplOptions.AggressiveInlining)]
794 return (m_stateFlags & (Task.TASK_STATE_WAIT_COMPLETION_NOTIFICATION | Task.TASK_STATE_RAN_TO_COMPLETION))
795 != Task.TASK_STATE_RAN_TO_COMPLETION;
799 /// <summary>
800 /// Determines whether we should inform the debugger that we're ending a join with a task.
801 /// This should only be called if the debugger notification bit is set, as it is has some cost,
802 /// namely it is a virtual call (however calling it if the bit is not set is not functionally
803 /// harmful). Derived implementations may choose to only conditionally call down to this base
804 /// implementation.
805 /// </summary>
806 internal virtual bool ShouldNotifyDebuggerOfWaitCompletion // ideally would be familyAndAssembly, but that can't be done in C#
810 // It's theoretically possible but extremely rare that this assert could fire because the
811 // bit was unset between the time that it was checked and this method was called.
812 // It's so remote a chance that it's worth having the assert to protect against misuse.
813 bool isWaitNotificationEnabled = IsWaitNotificationEnabled;
814 Debug.Assert(isWaitNotificationEnabled, "Should only be called if the wait completion bit is set.");
815 return isWaitNotificationEnabled;
819 /// <summary>Gets whether the task's debugger notification for wait completion bit is set.</summary>
820 /// <returns>true if the bit is set; false if it's not set.</returns>
821 internal bool IsWaitNotificationEnabled => // internal only to enable unit tests; would otherwise be private
822 (m_stateFlags & TASK_STATE_WAIT_COMPLETION_NOTIFICATION) != 0;
824 /// <summary>Placeholder method used as a breakpoint target by the debugger. Must not be inlined or optimized.</summary>
825 /// <remarks>All joins with a task should end up calling this if their debugger notification bit is set.</remarks>
826 [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
827 private void NotifyDebuggerOfWaitCompletion()
829 // It's theoretically possible but extremely rare that this assert could fire because the
830 // bit was unset between the time that it was checked and this method was called.
831 // It's so remote a chance that it's worth having the assert to protect against misuse.
832 Debug.Assert(IsWaitNotificationEnabled, "Should only be called if the wait completion bit is set.");
834 // Now that we're notifying the debugger, clear the bit. The debugger should do this anyway,
835 // but this adds a bit of protection in case it fails to, and given that the debugger is involved,
836 // the overhead here for the interlocked is negligable. We do still rely on the debugger
837 // to clear bits, as this doesn't recursively clear bits in the case of, for example, WhenAny.
838 SetNotificationForWaitCompletion(enabled: false);
842 // Atomically mark a Task as started while making sure that it is not canceled.
843 internal bool MarkStarted()
845 return AtomicStateUpdate(TASK_STATE_STARTED, TASK_STATE_CANCELED | TASK_STATE_STARTED);
848 internal void FireTaskScheduledIfNeeded(TaskScheduler ts)
850 if ((m_stateFlags & Task.TASK_STATE_TASKSCHEDULED_WAS_FIRED) == 0)
852 m_stateFlags |= Task.TASK_STATE_TASKSCHEDULED_WAS_FIRED;
854 Task? currentTask = Task.InternalCurrent;
855 Task? parentTask = m_contingentProperties?.m_parent;
856 TplEventSource.Log.TaskScheduled(ts.Id, currentTask == null ? 0 : currentTask.Id,
857 this.Id, parentTask == null ? 0 : parentTask.Id, (int)this.Options);
861 /// <summary>
862 /// Internal function that will be called by a new child task to add itself to
863 /// the children list of the parent (this).
865 /// Since a child task can only be created from the thread executing the action delegate
866 /// of this task, reentrancy is neither required nor supported. This should not be called from
867 /// anywhere other than the task construction/initialization codepaths.
868 /// </summary>
869 internal void AddNewChild()
871 Debug.Assert(Task.InternalCurrent == this, "Task.AddNewChild(): Called from an external context");
873 ContingentProperties props = EnsureContingentPropertiesInitialized();
875 if (props.m_completionCountdown == 1)
877 // A count of 1 indicates so far there was only the parent, and this is the first child task
878 // Single kid => no fuss about who else is accessing the count. Let's save ourselves 100 cycles
879 props.m_completionCountdown++;
881 else
883 // otherwise do it safely
884 Interlocked.Increment(ref props.m_completionCountdown);
888 // This is called in the case where a new child is added, but then encounters a CancellationToken-related exception.
889 // We need to subtract that child from m_completionCountdown, or the parent will never complete.
890 internal void DisregardChild()
892 Debug.Assert(Task.InternalCurrent == this, "Task.DisregardChild(): Called from an external context");
894 ContingentProperties props = EnsureContingentPropertiesInitialized();
895 Debug.Assert(props.m_completionCountdown >= 2, "Task.DisregardChild(): Expected parent count to be >= 2");
896 Interlocked.Decrement(ref props.m_completionCountdown);
899 /// <summary>
900 /// Starts the <see cref="Task"/>, scheduling it for execution to the current <see
901 /// cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see>.
902 /// </summary>
903 /// <remarks>
904 /// A task may only be started and run only once. Any attempts to schedule a task a second time
905 /// will result in an exception.
906 /// </remarks>
907 /// <exception cref="InvalidOperationException">
908 /// The <see cref="Task"/> is not in a valid state to be started. It may have already been started,
909 /// executed, or canceled, or it may have been created in a manner that doesn't support direct
910 /// scheduling.
911 /// </exception>
912 public void Start()
914 Start(TaskScheduler.Current);
917 /// <summary>
918 /// Starts the <see cref="Task"/>, scheduling it for execution to the specified <see
919 /// cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see>.
920 /// </summary>
921 /// <remarks>
922 /// A task may only be started and run only once. Any attempts to schedule a task a second time will
923 /// result in an exception.
924 /// </remarks>
925 /// <param name="scheduler">
926 /// The <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> with which to associate
927 /// and execute this task.
928 /// </param>
929 /// <exception cref="ArgumentNullException">
930 /// The <paramref name="scheduler"/> argument is null.
931 /// </exception>
932 /// <exception cref="InvalidOperationException">
933 /// The <see cref="Task"/> is not in a valid state to be started. It may have already been started,
934 /// executed, or canceled, or it may have been created in a manner that doesn't support direct
935 /// scheduling.
936 /// </exception>
937 public void Start(TaskScheduler scheduler)
939 // Read the volatile m_stateFlags field once and cache it for subsequent operations
940 int flags = m_stateFlags;
942 // Need to check this before (m_action == null) because completed tasks will
943 // set m_action to null. We would want to know if this is the reason that m_action == null.
944 if (IsCompletedMethod(flags))
946 ThrowHelper.ThrowInvalidOperationException(ExceptionResource.Task_Start_TaskCompleted);
949 if (scheduler == null)
951 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler);
954 TaskCreationOptions options = OptionsMethod(flags);
955 if ((options & (TaskCreationOptions)InternalTaskOptions.PromiseTask) != 0)
957 ThrowHelper.ThrowInvalidOperationException(ExceptionResource.Task_Start_Promise);
959 if ((options & (TaskCreationOptions)InternalTaskOptions.ContinuationTask) != 0)
961 ThrowHelper.ThrowInvalidOperationException(ExceptionResource.Task_Start_ContinuationTask);
964 // Make sure that Task only gets started once. Or else throw an exception.
965 if (Interlocked.CompareExchange(ref m_taskScheduler, scheduler, null) != null)
967 ThrowHelper.ThrowInvalidOperationException(ExceptionResource.Task_Start_AlreadyStarted);
970 ScheduleAndStart(true);
973 /// <summary>
974 /// Runs the <see cref="Task"/> synchronously on the current <see
975 /// cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see>.
976 /// </summary>
977 /// <remarks>
978 /// <para>
979 /// A task may only be started and run only once. Any attempts to schedule a task a second time will
980 /// result in an exception.
981 /// </para>
982 /// <para>
983 /// Tasks executed with <see cref="RunSynchronously()"/> will be associated with the current <see
984 /// cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see>.
985 /// </para>
986 /// <para>
987 /// If the target scheduler does not support running this Task on the current thread, the Task will
988 /// be scheduled for execution on the scheduler, and the current thread will block until the
989 /// Task has completed execution.
990 /// </para>
991 /// </remarks>
992 /// <exception cref="InvalidOperationException">
993 /// The <see cref="Task"/> is not in a valid state to be started. It may have already been started,
994 /// executed, or canceled, or it may have been created in a manner that doesn't support direct
995 /// scheduling.
996 /// </exception>
997 public void RunSynchronously()
999 InternalRunSynchronously(TaskScheduler.Current, waitForCompletion: true);
1002 /// <summary>
1003 /// Runs the <see cref="Task"/> synchronously on the <see
1004 /// cref="System.Threading.Tasks.TaskScheduler">scheduler</see> provided.
1005 /// </summary>
1006 /// <remarks>
1007 /// <para>
1008 /// A task may only be started and run only once. Any attempts to schedule a task a second time will
1009 /// result in an exception.
1010 /// </para>
1011 /// <para>
1012 /// If the target scheduler does not support running this Task on the current thread, the Task will
1013 /// be scheduled for execution on the scheduler, and the current thread will block until the
1014 /// Task has completed execution.
1015 /// </para>
1016 /// </remarks>
1017 /// <exception cref="InvalidOperationException">
1018 /// The <see cref="Task"/> is not in a valid state to be started. It may have already been started,
1019 /// executed, or canceled, or it may have been created in a manner that doesn't support direct
1020 /// scheduling.
1021 /// </exception>
1022 /// <exception cref="ArgumentNullException">The <paramref name="scheduler"/> parameter
1023 /// is null.</exception>
1024 /// <param name="scheduler">The scheduler on which to attempt to run this task inline.</param>
1025 public void RunSynchronously(TaskScheduler scheduler)
1027 if (scheduler == null)
1029 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler);
1032 InternalRunSynchronously(scheduler, waitForCompletion: true);
1036 // Internal version of RunSynchronously that allows not waiting for completion.
1038 internal void InternalRunSynchronously(TaskScheduler scheduler, bool waitForCompletion)
1040 Debug.Assert(scheduler != null, "Task.InternalRunSynchronously(): null TaskScheduler");
1042 // Read the volatile m_stateFlags field once and cache it for subsequent operations
1043 int flags = m_stateFlags;
1045 // Can't call this method on a continuation task
1046 TaskCreationOptions options = OptionsMethod(flags);
1047 if ((options & (TaskCreationOptions)InternalTaskOptions.ContinuationTask) != 0)
1049 ThrowHelper.ThrowInvalidOperationException(ExceptionResource.Task_RunSynchronously_Continuation);
1052 // Can't call this method on a promise-style task
1053 if ((options & (TaskCreationOptions)InternalTaskOptions.PromiseTask) != 0)
1055 ThrowHelper.ThrowInvalidOperationException(ExceptionResource.Task_RunSynchronously_Promise);
1058 // Can't call this method on a task that has already completed
1059 if (IsCompletedMethod(flags))
1061 ThrowHelper.ThrowInvalidOperationException(ExceptionResource.Task_RunSynchronously_TaskCompleted);
1064 // Make sure that Task only gets started once. Or else throw an exception.
1065 if (Interlocked.CompareExchange(ref m_taskScheduler, scheduler, null) != null)
1067 ThrowHelper.ThrowInvalidOperationException(ExceptionResource.Task_RunSynchronously_AlreadyStarted);
1070 // execute only if we successfully cancel when concurrent cancel attempts are made.
1071 // otherwise throw an exception, because we've been canceled.
1072 if (MarkStarted())
1074 bool taskQueued = false;
1077 // We wrap TryRunInline() in a try/catch block and move an excepted task to Faulted here,
1078 // but not in Wait()/WaitAll()/FastWaitAll(). Here, we know for sure that the
1079 // task will not be subsequently scheduled (assuming that the scheduler adheres
1080 // to the guideline that an exception implies that no state change took place),
1081 // so it is safe to catch the exception and move the task to a final state. The
1082 // same cannot be said for Wait()/WaitAll()/FastWaitAll().
1083 if (!scheduler.TryRunInline(this, false))
1085 scheduler.InternalQueueTask(this);
1086 taskQueued = true; // only mark this after successfully queuing the task.
1089 // A successful TryRunInline doesn't guarantee completion, as there may be unfinished children.
1090 // Also if we queued the task above, the task may not be done yet.
1091 if (waitForCompletion && !IsCompleted)
1093 SpinThenBlockingWait(Timeout.Infinite, default);
1096 catch (Exception e)
1098 // we received an unexpected exception originating from a custom scheduler, which needs to be wrapped in a TSE and thrown
1099 if (!taskQueued)
1101 // We had a problem with TryRunInline() or QueueTask().
1102 // Record the exception, marking ourselves as Completed/Faulted.
1103 TaskSchedulerException tse = new TaskSchedulerException(e);
1104 AddException(tse);
1105 Finish(false);
1107 // Mark ourselves as "handled" to avoid crashing the finalizer thread if the caller neglects to
1108 // call Wait() on this task.
1109 // m_contingentProperties.m_exceptionsHolder *should* already exist after AddException()
1110 Debug.Assert(
1111 (m_contingentProperties != null) &&
1112 (m_contingentProperties.m_exceptionsHolder != null) &&
1113 (m_contingentProperties.m_exceptionsHolder.ContainsFaultList),
1114 "Task.InternalRunSynchronously(): Expected m_contingentProperties.m_exceptionsHolder to exist " +
1115 "and to have faults recorded.");
1116 m_contingentProperties.m_exceptionsHolder.MarkAsHandled(false);
1118 // And re-throw.
1119 throw tse;
1121 // We had a problem with waiting. Just re-throw.
1122 else throw;
1125 else
1127 Debug.Assert((m_stateFlags & TASK_STATE_CANCELED) != 0, "Task.RunSynchronously: expected TASK_STATE_CANCELED to be set");
1128 // Can't call this method on canceled task.
1129 ThrowHelper.ThrowInvalidOperationException(ExceptionResource.Task_RunSynchronously_TaskCompleted);
1134 ////
1135 //// Helper methods for Factory StartNew methods.
1136 ////
1139 // Implicitly converts action to object and handles the meat of the StartNew() logic.
1140 internal static Task InternalStartNew(
1141 Task? creatingTask, Delegate action, object? state, CancellationToken cancellationToken, TaskScheduler scheduler,
1142 TaskCreationOptions options, InternalTaskOptions internalOptions)
1144 // Validate arguments.
1145 if (scheduler == null)
1147 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler);
1150 // Create and schedule the task. This throws an InvalidOperationException if already shut down.
1151 // Here we add the InternalTaskOptions.QueuedByRuntime to the internalOptions, so that TaskConstructorCore can skip the cancellation token registration
1152 Task t = new Task(action, state, creatingTask, cancellationToken, options, internalOptions | InternalTaskOptions.QueuedByRuntime, scheduler);
1154 t.ScheduleAndStart(false);
1155 return t;
1158 /// <summary>
1159 /// Gets a unique ID for a <see cref="Task">Task</see> or task continuation instance.
1160 /// </summary>
1161 internal static int NewId()
1163 int newId;
1165 // We need to repeat if Interlocked.Increment wraps around and returns 0.
1166 // Otherwise next time this task's Id is queried it will get a new value
1169 newId = Interlocked.Increment(ref s_taskIdCounter);
1171 while (newId == 0);
1173 if (TplEventSource.Log.IsEnabled())
1174 TplEventSource.Log.NewID(newId);
1176 return newId;
1180 /////////////
1181 // properties
1183 /// <summary>
1184 /// Gets a unique ID for this <see cref="Task">Task</see> instance.
1185 /// </summary>
1186 /// <remarks>
1187 /// Task IDs are assigned on-demand and do not necessarily represent the order in the which Task
1188 /// instances were created.
1189 /// </remarks>
1190 public int Id
1194 if (m_taskId == 0)
1196 int newId = NewId();
1197 Interlocked.CompareExchange(ref m_taskId, newId, 0);
1200 return m_taskId;
1204 /// <summary>
1205 /// Returns the unique ID of the currently executing <see cref="Task">Task</see>.
1206 /// </summary>
1207 public static int? CurrentId
1211 Task? currentTask = InternalCurrent;
1212 if (currentTask != null)
1213 return currentTask.Id;
1214 else
1215 return null;
1219 /// <summary>
1220 /// Gets the <see cref="Task">Task</see> instance currently executing, or
1221 /// null if none exists.
1222 /// </summary>
1223 internal static Task? InternalCurrent => t_currentTask;
1225 /// <summary>
1226 /// Gets the Task instance currently executing if the specified creation options
1227 /// contain AttachedToParent.
1228 /// </summary>
1229 /// <param name="creationOptions">The options to check.</param>
1230 /// <returns>The current task if there is one and if AttachToParent is in the options; otherwise, null.</returns>
1231 internal static Task? InternalCurrentIfAttached(TaskCreationOptions creationOptions)
1233 return (creationOptions & TaskCreationOptions.AttachedToParent) != 0 ? InternalCurrent : null;
1236 /// <summary>
1237 /// Gets the <see cref="System.AggregateException">Exception</see> that caused the <see
1238 /// cref="Task">Task</see> to end prematurely. If the <see
1239 /// cref="Task">Task</see> completed successfully or has not yet thrown any
1240 /// exceptions, this will return null.
1241 /// </summary>
1242 /// <remarks>
1243 /// Tasks that throw unhandled exceptions store the resulting exception and propagate it wrapped in a
1244 /// <see cref="System.AggregateException"/> in calls to <see cref="Wait()">Wait</see>
1245 /// or in accesses to the <see cref="Exception"/> property. Any exceptions not observed by the time
1246 /// the Task instance is garbage collected will be propagated on the finalizer thread.
1247 /// </remarks>
1248 public AggregateException? Exception
1252 AggregateException? e = null;
1254 // If you're faulted, retrieve the exception(s)
1255 if (IsFaulted) e = GetExceptions(false);
1257 // Only return an exception in faulted state (skip manufactured exceptions)
1258 // A "benevolent" race condition makes it possible to return null when IsFaulted is
1259 // true (i.e., if IsFaulted is set just after the check to IsFaulted above).
1260 Debug.Assert((e == null) || IsFaulted, "Task.Exception_get(): returning non-null value when not Faulted");
1262 return e;
1266 /// <summary>
1267 /// Gets the <see cref="System.Threading.Tasks.TaskStatus">TaskStatus</see> of this Task.
1268 /// </summary>
1269 public TaskStatus Status
1273 TaskStatus rval;
1275 // get a cached copy of the state flags. This should help us
1276 // to get a consistent view of the flags if they are changing during the
1277 // execution of this method.
1278 int sf = m_stateFlags;
1280 if ((sf & TASK_STATE_FAULTED) != 0) rval = TaskStatus.Faulted;
1281 else if ((sf & TASK_STATE_CANCELED) != 0) rval = TaskStatus.Canceled;
1282 else if ((sf & TASK_STATE_RAN_TO_COMPLETION) != 0) rval = TaskStatus.RanToCompletion;
1283 else if ((sf & TASK_STATE_WAITING_ON_CHILDREN) != 0) rval = TaskStatus.WaitingForChildrenToComplete;
1284 else if ((sf & TASK_STATE_DELEGATE_INVOKED) != 0) rval = TaskStatus.Running;
1285 else if ((sf & TASK_STATE_STARTED) != 0) rval = TaskStatus.WaitingToRun;
1286 else if ((sf & TASK_STATE_WAITINGFORACTIVATION) != 0) rval = TaskStatus.WaitingForActivation;
1287 else rval = TaskStatus.Created;
1289 return rval;
1293 /// <summary>
1294 /// Gets whether this <see cref="Task">Task</see> instance has completed
1295 /// execution due to being canceled.
1296 /// </summary>
1297 /// <remarks>
1298 /// A <see cref="Task">Task</see> will complete in Canceled state either if its <see cref="CancellationToken">CancellationToken</see>
1299 /// was marked for cancellation before the task started executing, or if the task acknowledged the cancellation request on
1300 /// its already signaled CancellationToken by throwing an
1301 /// <see cref="System.OperationCanceledException">OperationCanceledException</see> that bears the same
1302 /// <see cref="System.Threading.CancellationToken">CancellationToken</see>.
1303 /// </remarks>
1304 public bool IsCanceled =>
1305 // Return true if canceled bit is set and faulted bit is not set
1306 (m_stateFlags & (TASK_STATE_CANCELED | TASK_STATE_FAULTED)) == TASK_STATE_CANCELED;
1308 /// <summary>
1309 /// Returns true if this task has a cancellation token and it was signaled.
1310 /// To be used internally in execute entry codepaths.
1311 /// </summary>
1312 internal bool IsCancellationRequested
1316 // check both the internal cancellation request flag and the CancellationToken attached to this task
1317 ContingentProperties? props = Volatile.Read(ref m_contingentProperties);
1318 return props != null &&
1319 (props.m_internalCancellationRequested == CANCELLATION_REQUESTED ||
1320 props.m_cancellationToken.IsCancellationRequested);
1324 /// <summary>
1325 /// Ensures that the contingent properties field has been initialized.
1326 /// ASSUMES THAT m_stateFlags IS ALREADY SET!
1327 /// </summary>
1328 /// <returns>The initialized contingent properties object.</returns>
1329 internal ContingentProperties EnsureContingentPropertiesInitialized()
1331 return LazyInitializer.EnsureInitialized(ref m_contingentProperties, () => new ContingentProperties());
1334 /// <summary>
1335 /// Without synchronization, ensures that the contingent properties field has been initialized.
1336 /// ASSUMES THAT m_stateFlags IS ALREADY SET!
1337 /// </summary>
1338 /// <returns>The initialized contingent properties object.</returns>
1339 internal ContingentProperties EnsureContingentPropertiesInitializedUnsafe()
1341 return m_contingentProperties ?? (m_contingentProperties = new ContingentProperties());
1344 /// <summary>
1345 /// This internal property provides access to the CancellationToken that was set on the task
1346 /// when it was constructed.
1347 /// </summary>
1348 internal CancellationToken CancellationToken
1352 ContingentProperties? props = Volatile.Read(ref m_contingentProperties);
1353 return (props == null) ? default : props.m_cancellationToken;
1357 /// <summary>
1358 /// Gets whether this <see cref="Task"/> threw an OperationCanceledException while its CancellationToken was signaled.
1359 /// </summary>
1360 internal bool IsCancellationAcknowledged => (m_stateFlags & TASK_STATE_CANCELLATIONACKNOWLEDGED) != 0;
1363 /// <summary>
1364 /// Gets whether this <see cref="Task">Task</see> has completed.
1365 /// </summary>
1366 /// <remarks>
1367 /// <see cref="IsCompleted"/> will return true when the Task is in one of the three
1368 /// final states: <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see>,
1369 /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>, or
1370 /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see>.
1371 /// </remarks>
1372 public bool IsCompleted
1376 int stateFlags = m_stateFlags; // enable inlining of IsCompletedMethod by "cast"ing away the volatility
1377 return IsCompletedMethod(stateFlags);
1381 // Similar to IsCompleted property, but allows for the use of a cached flags value
1382 // rather than reading the volatile m_stateFlags field.
1383 private static bool IsCompletedMethod(int flags)
1385 return (flags & TASK_STATE_COMPLETED_MASK) != 0;
1388 public bool IsCompletedSuccessfully => (m_stateFlags & TASK_STATE_COMPLETED_MASK) == TASK_STATE_RAN_TO_COMPLETION;
1390 /// <summary>
1391 /// Gets the <see cref="System.Threading.Tasks.TaskCreationOptions">TaskCreationOptions</see> used
1392 /// to create this task.
1393 /// </summary>
1394 public TaskCreationOptions CreationOptions => Options & (TaskCreationOptions)(~InternalTaskOptions.InternalOptionsMask);
1396 /// <summary>
1397 /// Gets a <see cref="System.Threading.WaitHandle"/> that can be used to wait for the task to
1398 /// complete.
1399 /// </summary>
1400 /// <remarks>
1401 /// Using the wait functionality provided by <see cref="Wait()"/>
1402 /// should be preferred over using <see cref="IAsyncResult.AsyncWaitHandle"/> for similar
1403 /// functionality.
1404 /// </remarks>
1405 /// <exception cref="System.ObjectDisposedException">
1406 /// The <see cref="Task"/> has been disposed.
1407 /// </exception>
1408 WaitHandle IAsyncResult.AsyncWaitHandle
1410 // Although a slim event is used internally to avoid kernel resource allocation, this function
1411 // forces allocation of a true WaitHandle when called.
1414 bool isDisposed = (m_stateFlags & TASK_STATE_DISPOSED) != 0;
1415 if (isDisposed)
1417 ThrowHelper.ThrowObjectDisposedException(ExceptionResource.Task_ThrowIfDisposed);
1419 return CompletedEvent.WaitHandle;
1423 /// <summary>
1424 /// Gets the state object supplied when the <see cref="Task">Task</see> was created,
1425 /// or null if none was supplied.
1426 /// </summary>
1427 public object? AsyncState => m_stateObject;
1429 /// <summary>
1430 /// Gets an indication of whether the asynchronous operation completed synchronously.
1431 /// </summary>
1432 /// <value>true if the asynchronous operation completed synchronously; otherwise, false.</value>
1433 bool IAsyncResult.CompletedSynchronously => false;
1435 /// <summary>
1436 /// Provides access to the TaskScheduler responsible for executing this Task.
1437 /// </summary>
1438 internal TaskScheduler? ExecutingTaskScheduler => m_taskScheduler;
1440 /// <summary>
1441 /// Provides access to factory methods for creating <see cref="Task"/> and <see cref="Task{TResult}"/> instances.
1442 /// </summary>
1443 /// <remarks>
1444 /// The factory returned from <see cref="Factory"/> is a default instance
1445 /// of <see cref="System.Threading.Tasks.TaskFactory"/>, as would result from using
1446 /// the default constructor on TaskFactory.
1447 /// </remarks>
1448 public static TaskFactory Factory { get; } = new TaskFactory();
1450 /// <summary>Gets a task that's already been completed successfully.</summary>
1451 public static Task CompletedTask { get; } = new Task(false, (TaskCreationOptions)InternalTaskOptions.DoNotDispose, default);
1453 /// <summary>
1454 /// Provides an event that can be used to wait for completion.
1455 /// Only called by IAsyncResult.AsyncWaitHandle, which means that we really do need to instantiate a completion event.
1456 /// </summary>
1457 internal ManualResetEventSlim CompletedEvent
1461 ContingentProperties contingentProps = EnsureContingentPropertiesInitialized();
1462 if (contingentProps.m_completionEvent == null)
1464 bool wasCompleted = IsCompleted;
1465 ManualResetEventSlim newMre = new ManualResetEventSlim(wasCompleted);
1466 if (Interlocked.CompareExchange(ref contingentProps.m_completionEvent, newMre, null) != null)
1468 // Someone else already set the value, so we will just close the event right away.
1469 newMre.Dispose();
1471 else if (!wasCompleted && IsCompleted)
1473 // We published the event as unset, but the task has subsequently completed.
1474 // Set the event's state properly so that callers don't deadlock.
1475 newMre.Set();
1479 return contingentProps.m_completionEvent;
1484 /// <summary>
1485 /// Whether an exception has been stored into the task.
1486 /// </summary>
1487 internal bool ExceptionRecorded
1491 ContingentProperties? props = Volatile.Read(ref m_contingentProperties);
1492 return (props != null) && (props.m_exceptionsHolder != null) && (props.m_exceptionsHolder.ContainsFaultList);
1496 /// <summary>
1497 /// Gets whether the <see cref="Task"/> completed due to an unhandled exception.
1498 /// </summary>
1499 /// <remarks>
1500 /// If <see cref="IsFaulted"/> is true, the Task's <see cref="Status"/> will be equal to
1501 /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">TaskStatus.Faulted</see>, and its
1502 /// <see cref="Exception"/> property will be non-null.
1503 /// </remarks>
1504 public bool IsFaulted =>
1505 // Faulted is "king" -- if that bit is present (regardless of other bits), we are faulted.
1506 ((m_stateFlags & TASK_STATE_FAULTED) != 0);
1508 /// <summary>
1509 /// The captured execution context for the current task to run inside
1510 /// If the TASK_STATE_EXECUTIONCONTEXT_IS_NULL flag is set, this means ExecutionContext.Capture returned null, otherwise
1511 /// If the captured context is the default, nothing is saved, otherwise the m_contingentProperties inflates to save the context
1512 /// </summary>
1513 internal ExecutionContext? CapturedContext
1517 if ((m_stateFlags & TASK_STATE_EXECUTIONCONTEXT_IS_NULL) == TASK_STATE_EXECUTIONCONTEXT_IS_NULL)
1519 return null;
1521 else
1523 return m_contingentProperties?.m_capturedContext ?? ExecutionContext.Default;
1528 // There is no need to atomically set this bit because this set() method is only called during construction, and therefore there should be no contending accesses to m_stateFlags
1529 if (value == null)
1531 m_stateFlags |= TASK_STATE_EXECUTIONCONTEXT_IS_NULL;
1533 else if (value != ExecutionContext.Default) // not the default context, then inflate the contingent properties and set it
1535 EnsureContingentPropertiesInitializedUnsafe().m_capturedContext = value;
1537 //else do nothing, this is the default context
1541 /////////////
1542 // methods
1545 /// <summary>
1546 /// Disposes the <see cref="Task"/>, releasing all of its unmanaged resources.
1547 /// </summary>
1548 /// <remarks>
1549 /// Unlike most of the members of <see cref="Task"/>, this method is not thread-safe.
1550 /// Also, <see cref="Dispose()"/> may only be called on a <see cref="Task"/> that is in one of
1551 /// the final states: <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see>,
1552 /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>, or
1553 /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see>.
1554 /// </remarks>
1555 /// <exception cref="System.InvalidOperationException">
1556 /// The exception that is thrown if the <see cref="Task"/> is not in
1557 /// one of the final states: <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see>,
1558 /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>, or
1559 /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see>.
1560 /// </exception>
1561 public void Dispose()
1563 Dispose(true);
1564 GC.SuppressFinalize(this);
1567 /// <summary>
1568 /// Disposes the <see cref="Task"/>, releasing all of its unmanaged resources.
1569 /// </summary>
1570 /// <param name="disposing">
1571 /// A Boolean value that indicates whether this method is being called due to a call to <see
1572 /// cref="Dispose()"/>.
1573 /// </param>
1574 /// <remarks>
1575 /// Unlike most of the members of <see cref="Task"/>, this method is not thread-safe.
1576 /// </remarks>
1577 protected virtual void Dispose(bool disposing)
1579 if (disposing)
1581 // Dispose is a nop if this task was created with the DoNotDispose internal option.
1582 // This is done before the completed check, because if we're not touching any
1583 // state on the task, it's ok for it to happen before completion.
1584 if ((Options & (TaskCreationOptions)InternalTaskOptions.DoNotDispose) != 0)
1586 return;
1589 // Task must be completed to dispose
1590 if (!IsCompleted)
1592 ThrowHelper.ThrowInvalidOperationException(ExceptionResource.Task_Dispose_NotCompleted);
1595 // Dispose of the underlying completion event if it exists
1596 ContingentProperties? cp = Volatile.Read(ref m_contingentProperties);
1597 if (cp != null)
1599 // Make a copy to protect against racing Disposes.
1600 // If we wanted to make this a bit safer, we could use an interlocked here,
1601 // but we state that Dispose is not thread safe.
1602 ManualResetEventSlim? ev = cp.m_completionEvent;
1603 if (ev != null)
1605 // Null out the completion event in contingent props; we'll use our copy from here on out
1606 cp.m_completionEvent = null;
1608 // In the unlikely event that our completion event is inflated but not yet signaled,
1609 // go ahead and signal the event. If you dispose of an unsignaled MRES, then any waiters
1610 // will deadlock; an ensuing Set() will not wake them up. In the event of an AppDomainUnload,
1611 // there is no guarantee that anyone else is going to signal the event, and it does no harm to
1612 // call Set() twice on m_completionEvent.
1613 if (!ev.IsSet) ev.Set();
1615 // Finally, dispose of the event
1616 ev.Dispose();
1621 // We OR the flags to indicate the object has been disposed. The task
1622 // has already completed at this point, and the only conceivable race condition would
1623 // be with the unsetting of the TASK_STATE_WAIT_COMPLETION_NOTIFICATION flag, which
1624 // is extremely unlikely and also benign. (Worst case: we hit a breakpoint
1625 // twice instead of once in the debugger. Weird, but not lethal.)
1626 m_stateFlags |= TASK_STATE_DISPOSED;
1629 /////////////
1630 // internal helpers
1633 /// <summary>
1634 /// Schedules the task for execution.
1635 /// </summary>
1636 /// <param name="needsProtection">If true, TASK_STATE_STARTED bit is turned on in
1637 /// an atomic fashion, making sure that TASK_STATE_CANCELED does not get set
1638 /// underneath us. If false, TASK_STATE_STARTED bit is OR-ed right in. This
1639 /// allows us to streamline things a bit for StartNew(), where competing cancellations
1640 /// are not a problem.</param>
1641 internal void ScheduleAndStart(bool needsProtection)
1643 Debug.Assert(m_taskScheduler != null, "expected a task scheduler to have been selected");
1644 Debug.Assert((m_stateFlags & TASK_STATE_STARTED) == 0, "task has already started");
1646 // Set the TASK_STATE_STARTED bit
1647 if (needsProtection)
1649 if (!MarkStarted())
1651 // A cancel has snuck in before we could get started. Quietly exit.
1652 return;
1655 else
1657 m_stateFlags |= TASK_STATE_STARTED;
1660 if (s_asyncDebuggingEnabled)
1661 AddToActiveTasks(this);
1663 if (AsyncCausalityTracer.LoggingOn && (Options & (TaskCreationOptions)InternalTaskOptions.ContinuationTask) == 0)
1665 //For all other task than TaskContinuations we want to log. TaskContinuations log in their constructor
1666 Debug.Assert(m_action != null, "Must have a delegate to be in ScheduleAndStart");
1667 AsyncCausalityTracer.TraceOperationCreation(this, "Task: " + m_action.Method.Name);
1673 // Queue to the indicated scheduler.
1674 m_taskScheduler.InternalQueueTask(this);
1676 catch (Exception e)
1678 // The scheduler had a problem queueing this task. Record the exception, leaving this task in
1679 // a Faulted state.
1680 TaskSchedulerException tse = new TaskSchedulerException(e);
1681 AddException(tse);
1682 Finish(false);
1684 // Now we need to mark ourselves as "handled" to avoid crashing the finalizer thread if we are called from StartNew(),
1685 // because the exception is either propagated outside directly, or added to an enclosing parent. However we won't do this for
1686 // continuation tasks, because in that case we internally eat the exception and therefore we need to make sure the user does
1687 // later observe it explicitly or see it on the finalizer.
1689 if ((Options & (TaskCreationOptions)InternalTaskOptions.ContinuationTask) == 0)
1691 // m_contingentProperties.m_exceptionsHolder *should* already exist after AddException()
1692 Debug.Assert(
1693 (m_contingentProperties != null) &&
1694 (m_contingentProperties.m_exceptionsHolder != null) &&
1695 (m_contingentProperties.m_exceptionsHolder.ContainsFaultList),
1696 "Task.ScheduleAndStart(): Expected m_contingentProperties.m_exceptionsHolder to exist " +
1697 "and to have faults recorded.");
1699 m_contingentProperties.m_exceptionsHolder.MarkAsHandled(false);
1701 // re-throw the exception wrapped as a TaskSchedulerException.
1702 throw tse;
1706 /// <summary>
1707 /// Adds an exception to the list of exceptions this task has thrown.
1708 /// </summary>
1709 /// <param name="exceptionObject">An object representing either an Exception or a collection of Exceptions.</param>
1710 internal void AddException(object exceptionObject)
1712 Debug.Assert(exceptionObject != null, "Task.AddException: Expected a non-null exception object");
1713 AddException(exceptionObject, representsCancellation: false);
1716 /// <summary>
1717 /// Adds an exception to the list of exceptions this task has thrown.
1718 /// </summary>
1719 /// <param name="exceptionObject">An object representing either an Exception or a collection of Exceptions.</param>
1720 /// <param name="representsCancellation">Whether the exceptionObject is an OperationCanceledException representing cancellation.</param>
1721 internal void AddException(object exceptionObject, bool representsCancellation)
1723 Debug.Assert(exceptionObject != null, "Task.AddException: Expected a non-null exception object");
1725 #if DEBUG
1726 var eoAsException = exceptionObject as Exception;
1727 var eoAsEnumerableException = exceptionObject as IEnumerable<Exception>;
1728 var eoAsEdi = exceptionObject as ExceptionDispatchInfo;
1729 var eoAsEnumerableEdi = exceptionObject as IEnumerable<ExceptionDispatchInfo>;
1731 Debug.Assert(
1732 eoAsException != null || eoAsEnumerableException != null || eoAsEdi != null || eoAsEnumerableEdi != null,
1733 "Task.AddException: Expected an Exception, ExceptionDispatchInfo, or an IEnumerable<> of one of those");
1735 var eoAsOce = exceptionObject as OperationCanceledException;
1737 Debug.Assert(
1738 !representsCancellation ||
1739 eoAsOce != null ||
1740 (eoAsEdi != null && eoAsEdi.SourceException is OperationCanceledException),
1741 "representsCancellation should be true only if an OCE was provided.");
1742 #endif
1745 // WARNING: A great deal of care went into ensuring that
1746 // AddException() and GetExceptions() are never called
1747 // simultaneously. See comment at start of GetExceptions().
1750 // Lazily initialize the holder, ensuring only one thread wins.
1751 ContingentProperties props = EnsureContingentPropertiesInitialized();
1752 if (props.m_exceptionsHolder == null)
1754 TaskExceptionHolder holder = new TaskExceptionHolder(this);
1755 if (Interlocked.CompareExchange(ref props.m_exceptionsHolder, holder, null) != null)
1757 // If someone else already set the value, suppress finalization.
1758 holder.MarkAsHandled(false);
1762 lock (props)
1764 props.m_exceptionsHolder.Add(exceptionObject, representsCancellation);
1768 /// <summary>
1769 /// Returns a list of exceptions by aggregating the holder's contents. Or null if
1770 /// no exceptions have been thrown.
1771 /// </summary>
1772 /// <param name="includeTaskCanceledExceptions">Whether to include a TCE if cancelled.</param>
1773 /// <returns>An aggregate exception, or null if no exceptions have been caught.</returns>
1774 private AggregateException? GetExceptions(bool includeTaskCanceledExceptions)
1777 // WARNING: The Task/Task<TResult>/TaskCompletionSource classes
1778 // have all been carefully crafted to insure that GetExceptions()
1779 // is never called while AddException() is being called. There
1780 // are locks taken on m_contingentProperties in several places:
1782 // -- Task<TResult>.TrySetException(): The lock allows the
1783 // task to be set to Faulted state, and all exceptions to
1784 // be recorded, in one atomic action.
1786 // -- Task.Exception_get(): The lock ensures that Task<TResult>.TrySetException()
1787 // is allowed to complete its operation before Task.Exception_get()
1788 // can access GetExceptions().
1790 // -- Task.ThrowIfExceptional(): The lock insures that Wait() will
1791 // not attempt to call GetExceptions() while Task<TResult>.TrySetException()
1792 // is in the process of calling AddException().
1794 // For "regular" tasks, we effectively keep AddException() and GetException()
1795 // from being called concurrently by the way that the state flows. Until
1796 // a Task is marked Faulted, Task.Exception_get() returns null. And
1797 // a Task is not marked Faulted until it and all of its children have
1798 // completed, which means that all exceptions have been recorded.
1800 // It might be a lot easier to follow all of this if we just required
1801 // that all calls to GetExceptions() and AddExceptions() were made
1802 // under a lock on m_contingentProperties. But that would also
1803 // increase our lock occupancy time and the frequency with which we
1804 // would need to take the lock.
1806 // If you add a call to GetExceptions() anywhere in the code,
1807 // please continue to maintain the invariant that it can't be
1808 // called when AddException() is being called.
1811 // We'll lazily create a TCE if the task has been canceled.
1812 Exception? canceledException = null;
1813 if (includeTaskCanceledExceptions && IsCanceled)
1815 // Backcompat:
1816 // Ideally we'd just use the cached OCE from this.GetCancellationExceptionDispatchInfo()
1817 // here. However, that would result in a potentially breaking change from .NET 4, which
1818 // has the code here that throws a new exception instead of the original, and the EDI
1819 // may not contain a TCE, but an OCE or any OCE-derived type, which would mean we'd be
1820 // propagating an exception of a different type.
1821 canceledException = new TaskCanceledException(this);
1824 if (ExceptionRecorded)
1826 // There are exceptions; get the aggregate and optionally add the canceled
1827 // exception to the aggregate (if applicable).
1828 Debug.Assert(m_contingentProperties != null && m_contingentProperties.m_exceptionsHolder != null, "ExceptionRecorded should imply this");
1830 // No need to lock around this, as other logic prevents the consumption of exceptions
1831 // before they have been completely processed.
1832 return m_contingentProperties.m_exceptionsHolder.CreateExceptionObject(false, canceledException);
1834 else if (canceledException != null)
1836 // No exceptions, but there was a cancelation. Aggregate and return it.
1837 return new AggregateException(canceledException);
1840 return null;
1843 /// <summary>Gets the exception dispatch infos once the task has faulted.</summary>
1844 internal ReadOnlyCollection<ExceptionDispatchInfo> GetExceptionDispatchInfos()
1846 Debug.Assert(IsFaulted && ExceptionRecorded, "Must only be used when the task has faulted with exceptions.");
1847 return m_contingentProperties!.m_exceptionsHolder!.GetExceptionDispatchInfos();
1850 /// <summary>Gets the ExceptionDispatchInfo containing the OperationCanceledException for this task.</summary>
1851 /// <returns>The ExceptionDispatchInfo. May be null if no OCE was stored for the task.</returns>
1852 internal ExceptionDispatchInfo? GetCancellationExceptionDispatchInfo()
1854 Debug.Assert(IsCanceled, "Must only be used when the task has canceled.");
1855 return Volatile.Read(ref m_contingentProperties)?.m_exceptionsHolder?.GetCancellationExceptionDispatchInfo(); // may be null
1858 /// <summary>
1859 /// Throws an aggregate exception if the task contains exceptions.
1860 /// </summary>
1861 internal void ThrowIfExceptional(bool includeTaskCanceledExceptions)
1863 Debug.Assert(IsCompleted, "ThrowIfExceptional(): Expected IsCompleted == true");
1865 Exception? exception = GetExceptions(includeTaskCanceledExceptions);
1866 if (exception != null)
1868 UpdateExceptionObservedStatus();
1869 throw exception;
1873 /// <summary>Throws the exception on the ThreadPool.</summary>
1874 /// <param name="exception">The exception to propagate.</param>
1875 /// <param name="targetContext">The target context on which to propagate the exception. Null to use the ThreadPool.</param>
1876 internal static void ThrowAsync(Exception exception, SynchronizationContext? targetContext)
1878 // Capture the exception into an ExceptionDispatchInfo so that its
1879 // stack trace and Watson bucket info will be preserved
1880 var edi = ExceptionDispatchInfo.Capture(exception);
1882 // If the user supplied a SynchronizationContext...
1883 if (targetContext != null)
1887 // Post the throwing of the exception to that context, and return.
1888 targetContext.Post(state => ((ExceptionDispatchInfo)state!).Throw(), edi);
1889 return;
1891 catch (Exception postException)
1893 // If something goes horribly wrong in the Post, we'll
1894 // propagate both exceptions on the ThreadPool
1895 edi = ExceptionDispatchInfo.Capture(new AggregateException(exception, postException));
1899 #if CORERT
1900 RuntimeExceptionHelpers.ReportUnhandledException(edi.SourceException);
1901 #else
1903 #if FEATURE_COMINTEROP
1904 // If we have the new error reporting APIs, report this error.
1905 if (System.Runtime.InteropServices.WindowsRuntime.WindowsRuntimeMarshal.ReportUnhandledError(edi.SourceException))
1906 return;
1907 #endif
1909 // Propagate the exception(s) on the ThreadPool
1910 ThreadPool.QueueUserWorkItem(state => ((ExceptionDispatchInfo)state!).Throw(), edi);
1912 #endif // CORERT
1915 /// <summary>
1916 /// Checks whether this is an attached task, and whether we are being called by the parent task.
1917 /// And sets the TASK_STATE_EXCEPTIONOBSERVEDBYPARENT status flag based on that.
1919 /// This is meant to be used internally when throwing an exception, and when WaitAll is gathering
1920 /// exceptions for tasks it waited on. If this flag gets set, the implicit wait on children
1921 /// will skip exceptions to prevent duplication.
1923 /// This should only be called when this task has completed with an exception
1925 /// </summary>
1926 internal void UpdateExceptionObservedStatus()
1928 Task? parent = m_contingentProperties?.m_parent;
1929 if ((parent != null)
1930 && ((Options & TaskCreationOptions.AttachedToParent) != 0)
1931 && ((parent.CreationOptions & TaskCreationOptions.DenyChildAttach) == 0)
1932 && Task.InternalCurrent == parent)
1934 m_stateFlags |= TASK_STATE_EXCEPTIONOBSERVEDBYPARENT;
1938 /// <summary>
1939 /// Checks whether the TASK_STATE_EXCEPTIONOBSERVEDBYPARENT status flag is set,
1940 /// This will only be used by the implicit wait to prevent double throws
1942 /// </summary>
1943 internal bool IsExceptionObservedByParent => (m_stateFlags & TASK_STATE_EXCEPTIONOBSERVEDBYPARENT) != 0;
1945 /// <summary>
1946 /// Checks whether the body was ever invoked. Used by task scheduler code to verify custom schedulers actually ran the task.
1947 /// </summary>
1948 internal bool IsDelegateInvoked => (m_stateFlags & TASK_STATE_DELEGATE_INVOKED) != 0;
1950 /// <summary>
1951 /// Signals completion of this particular task.
1953 /// The userDelegateExecute parameter indicates whether this Finish() call comes following the
1954 /// full execution of the user delegate.
1956 /// If userDelegateExecute is false, it mean user delegate wasn't invoked at all (either due to
1957 /// a cancellation request, or because this task is a promise style Task). In this case, the steps
1958 /// involving child tasks (i.e. WaitForChildren) will be skipped.
1960 /// </summary>
1961 internal void Finish(bool userDelegateExecute)
1963 if (m_contingentProperties == null)
1965 FinishStageTwo();
1967 else
1969 FinishSlow(userDelegateExecute);
1973 private void FinishSlow(bool userDelegateExecute)
1975 Debug.Assert(userDelegateExecute || m_contingentProperties != null);
1977 if (!userDelegateExecute)
1979 // delegate didn't execute => no children. We can safely call the remaining finish stages
1980 FinishStageTwo();
1982 else
1984 ContingentProperties props = m_contingentProperties!;
1986 // Count of 1 => either all children finished, or there were none. Safe to complete ourselves
1987 // without paying the price of an Interlocked.Decrement.
1988 if ((props.m_completionCountdown == 1) ||
1989 Interlocked.Decrement(ref props.m_completionCountdown) == 0) // Reaching this sub clause means there may be remaining active children,
1990 // and we could be racing with one of them to call FinishStageTwo().
1991 // So whoever does the final Interlocked.Dec is responsible to finish.
1993 FinishStageTwo();
1995 else
1997 // Apparently some children still remain. It will be up to the last one to process the completion of this task on their own thread.
1998 // We will now yield the thread back to ThreadPool. Mark our state appropriately before getting out.
2000 // We have to use an atomic update for this and make sure not to overwrite a final state,
2001 // because at this very moment the last child's thread may be concurrently completing us.
2002 // Otherwise we risk overwriting the TASK_STATE_RAN_TO_COMPLETION, _CANCELED or _FAULTED bit which may have been set by that child task.
2003 // Note that the concurrent update by the last child happening in FinishStageTwo could still wipe out the TASK_STATE_WAITING_ON_CHILDREN flag,
2004 // but it is not critical to maintain, therefore we dont' need to intruduce a full atomic update into FinishStageTwo
2006 AtomicStateUpdate(TASK_STATE_WAITING_ON_CHILDREN, TASK_STATE_FAULTED | TASK_STATE_CANCELED | TASK_STATE_RAN_TO_COMPLETION);
2009 // Now is the time to prune exceptional children. We'll walk the list and removes the ones whose exceptions we might have observed after they threw.
2010 // we use a local variable for exceptional children here because some other thread may be nulling out m_contingentProperties.m_exceptionalChildren
2011 List<Task>? exceptionalChildren = props.m_exceptionalChildren;
2012 if (exceptionalChildren != null)
2014 lock (exceptionalChildren)
2016 exceptionalChildren.RemoveAll(t => t.IsExceptionObservedByParent); // RemoveAll has better performance than doing it ourselves
2022 /// <summary>
2023 /// FinishStageTwo is to be executed as soon as we known there are no more children to complete.
2024 /// It can happen i) either on the thread that originally executed this task (if no children were spawned, or they all completed by the time this task's delegate quit)
2025 /// ii) or on the thread that executed the last child.
2026 /// </summary>
2027 private void FinishStageTwo()
2029 // At this point, the task is done executing and waiting for its children,
2030 // we can transition our task to a completion state.
2032 ContingentProperties? cp = Volatile.Read(ref m_contingentProperties);
2033 if (cp != null)
2035 AddExceptionsFromChildren(cp);
2038 int completionState;
2039 if (ExceptionRecorded)
2041 completionState = TASK_STATE_FAULTED;
2042 if (AsyncCausalityTracer.LoggingOn)
2043 AsyncCausalityTracer.TraceOperationCompletion(this, AsyncCausalityStatus.Error);
2045 if (s_asyncDebuggingEnabled)
2046 RemoveFromActiveTasks(this);
2048 else if (IsCancellationRequested && IsCancellationAcknowledged)
2050 // We transition into the TASK_STATE_CANCELED final state if the task's CT was signalled for cancellation,
2051 // and the user delegate acknowledged the cancellation request by throwing an OCE,
2052 // and the task hasn't otherwise transitioned into faulted state. (TASK_STATE_FAULTED trumps TASK_STATE_CANCELED)
2054 // If the task threw an OCE without cancellation being requestsed (while the CT not being in signaled state),
2055 // then we regard it as a regular exception
2057 completionState = TASK_STATE_CANCELED;
2058 if (AsyncCausalityTracer.LoggingOn)
2059 AsyncCausalityTracer.TraceOperationCompletion(this, AsyncCausalityStatus.Canceled);
2061 if (s_asyncDebuggingEnabled)
2062 RemoveFromActiveTasks(this);
2064 else
2066 completionState = TASK_STATE_RAN_TO_COMPLETION;
2067 if (AsyncCausalityTracer.LoggingOn)
2068 AsyncCausalityTracer.TraceOperationCompletion(this, AsyncCausalityStatus.Completed);
2070 if (s_asyncDebuggingEnabled)
2071 RemoveFromActiveTasks(this);
2074 // Use Interlocked.Exchange() to effect a memory fence, preventing
2075 // any SetCompleted() (or later) instructions from sneak back before it.
2076 Interlocked.Exchange(ref m_stateFlags, m_stateFlags | completionState);
2078 // Set the completion event if it's been lazy allocated.
2079 // And if we made a cancellation registration, it's now unnecessary.
2080 cp = Volatile.Read(ref m_contingentProperties); // need to re-read after updating state
2081 if (cp != null)
2083 cp.SetCompleted();
2084 cp.UnregisterCancellationCallback();
2087 // ready to run continuations and notify parent.
2088 FinishStageThree();
2092 /// <summary>
2093 /// Final stage of the task completion code path. Notifies the parent (if any) that another of its children are done, and runs continuations.
2094 /// This function is only separated out from FinishStageTwo because these two operations are also needed to be called from CancellationCleanupLogic()
2095 /// </summary>
2096 internal void FinishStageThree()
2098 // Release the action so that holding this task object alive doesn't also
2099 // hold alive the body of the task. We do this before notifying a parent,
2100 // so that if notifying the parent completes the parent and causes
2101 // its synchronous continuations to run, the GC can collect the state
2102 // in the interim. And we do it before finishing continuations, because
2103 // continuations hold onto the task, and therefore are keeping it alive.
2104 m_action = null;
2106 ContingentProperties? cp = m_contingentProperties;
2107 if (cp != null)
2109 // Similarly, null out any ExecutionContext we may have captured,
2110 // to avoid keeping state like async locals alive unnecessarily
2111 // when the Task is kept alive.
2112 cp.m_capturedContext = null;
2114 // Notify parent if this was an attached task
2115 NotifyParentIfPotentiallyAttachedTask();
2118 // Activate continuations (if any).
2119 FinishContinuations();
2122 internal void NotifyParentIfPotentiallyAttachedTask()
2124 Task? parent = m_contingentProperties?.m_parent;
2125 if (parent != null
2126 && ((parent.CreationOptions & TaskCreationOptions.DenyChildAttach) == 0)
2127 && (((TaskCreationOptions)(m_stateFlags & OptionsMask)) & TaskCreationOptions.AttachedToParent) != 0)
2129 parent.ProcessChildCompletion(this);
2133 /// <summary>
2134 /// This is called by children of this task when they are completed.
2135 /// </summary>
2136 internal void ProcessChildCompletion(Task childTask)
2138 Debug.Assert(childTask != null);
2139 Debug.Assert(childTask.IsCompleted, "ProcessChildCompletion was called for an uncompleted task");
2141 Debug.Assert(childTask.m_contingentProperties?.m_parent == this, "ProcessChildCompletion should only be called for a child of this task");
2143 ContingentProperties? props = Volatile.Read(ref m_contingentProperties);
2145 // if the child threw and we haven't observed it we need to save it for future reference
2146 if (childTask.IsFaulted && !childTask.IsExceptionObservedByParent)
2148 // Lazily initialize the child exception list
2149 if (props!.m_exceptionalChildren == null)
2151 Interlocked.CompareExchange(ref props.m_exceptionalChildren, new List<Task>(), null);
2154 // In rare situations involving AppDomainUnload, it's possible (though unlikely) for FinishStageTwo() to be called
2155 // multiple times for the same task. In that case, AddExceptionsFromChildren() could be nulling m_exceptionalChildren
2156 // out at the same time that we're processing it, resulting in a NullReferenceException here. We'll protect
2157 // ourselves by caching m_exceptionChildren in a local variable.
2158 List<Task>? tmp = props.m_exceptionalChildren;
2159 if (tmp != null)
2161 lock (tmp)
2163 tmp.Add(childTask);
2168 if (Interlocked.Decrement(ref props!.m_completionCountdown) == 0)
2170 // This call came from the final child to complete, and apparently we have previously given up this task's right to complete itself.
2171 // So we need to invoke the final finish stage.
2173 FinishStageTwo();
2177 /// <summary>
2178 /// This is to be called just before the task does its final state transition.
2179 /// It traverses the list of exceptional children, and appends their aggregate exceptions into this one's exception list
2180 /// </summary>
2181 internal void AddExceptionsFromChildren(ContingentProperties props)
2183 Debug.Assert(props != null);
2185 // In rare occurences during AppDomainUnload() processing, it is possible for this method to be called
2186 // simultaneously on the same task from two different contexts. This can result in m_exceptionalChildren
2187 // being nulled out while it is being processed, which could lead to a NullReferenceException. To
2188 // protect ourselves, we'll cache m_exceptionalChildren in a local variable.
2189 List<Task>? exceptionalChildren = props.m_exceptionalChildren;
2191 if (exceptionalChildren != null)
2193 // This lock is necessary because even though AddExceptionsFromChildren is last to execute, it may still
2194 // be racing with the code segment at the bottom of Finish() that prunes the exceptional child array.
2195 lock (exceptionalChildren)
2197 foreach (Task task in exceptionalChildren)
2199 // Ensure any exceptions thrown by children are added to the parent.
2200 // In doing this, we are implicitly marking children as being "handled".
2201 Debug.Assert(task.IsCompleted, "Expected all tasks in list to be completed");
2202 if (task.IsFaulted && !task.IsExceptionObservedByParent)
2204 TaskExceptionHolder? exceptionHolder = Volatile.Read(ref task.m_contingentProperties)!.m_exceptionsHolder;
2205 Debug.Assert(exceptionHolder != null);
2207 // No locking necessary since child task is finished adding exceptions
2208 // and concurrent CreateExceptionObject() calls do not constitute
2209 // a concurrency hazard.
2210 AddException(exceptionHolder.CreateExceptionObject(false, null));
2215 // Reduce memory pressure by getting rid of the array
2216 props.m_exceptionalChildren = null;
2220 /// <summary>
2221 /// Outermost entry function to execute this task. Handles all aspects of executing a task on the caller thread.
2222 /// </summary>
2223 internal bool ExecuteEntry()
2225 // Do atomic state transition from queued to invoked. If we observe a task that's already invoked,
2226 // we will return false so that TaskScheduler.ExecuteTask can throw an exception back to the custom scheduler.
2227 // However we don't want this exception to be throw if the task was already canceled, because it's a
2228 // legitimate scenario for custom schedulers to dequeue a task and mark it as canceled (example: throttling scheduler)
2229 int previousState = 0;
2230 if (!AtomicStateUpdate(TASK_STATE_DELEGATE_INVOKED,
2231 TASK_STATE_DELEGATE_INVOKED | TASK_STATE_COMPLETED_MASK,
2232 ref previousState) && (previousState & TASK_STATE_CANCELED) == 0)
2234 // This task has already been invoked. Don't invoke it again.
2235 return false;
2238 if (!IsCancellationRequested & !IsCanceled)
2240 ExecuteWithThreadLocal(ref t_currentTask);
2242 else
2244 ExecuteEntryCancellationRequestedOrCanceled();
2247 return true;
2250 /// <summary>
2251 /// ThreadPool's entry point into the Task. The base behavior is simply to
2252 /// use the entry point that's not protected from double-invoke; derived internal tasks
2253 /// can override to customize their behavior, which is usually done by promises
2254 /// that want to reuse the same object as a queued work item.
2255 /// </summary>
2256 internal virtual void ExecuteFromThreadPool(Thread threadPoolThread) => ExecuteEntryUnsafe(threadPoolThread);
2258 internal void ExecuteEntryUnsafe(Thread? threadPoolThread) // used instead of ExecuteEntry() when we don't have to worry about double-execution prevent
2260 // Remember that we started running the task delegate.
2261 m_stateFlags |= TASK_STATE_DELEGATE_INVOKED;
2263 if (!IsCancellationRequested & !IsCanceled)
2265 ExecuteWithThreadLocal(ref t_currentTask, threadPoolThread);
2267 else
2269 ExecuteEntryCancellationRequestedOrCanceled();
2273 internal void ExecuteEntryCancellationRequestedOrCanceled()
2275 if (!IsCanceled)
2277 int prevState = Interlocked.Exchange(ref m_stateFlags, m_stateFlags | TASK_STATE_CANCELED);
2278 if ((prevState & TASK_STATE_CANCELED) == 0)
2280 CancellationCleanupLogic();
2285 // A trick so we can refer to the TLS slot with a byref.
2286 private void ExecuteWithThreadLocal(ref Task? currentTaskSlot, Thread? threadPoolThread = null)
2288 // Remember the current task so we can restore it after running, and then
2289 Task? previousTask = currentTaskSlot;
2291 // ETW event for Task Started
2292 TplEventSource log = TplEventSource.Log;
2293 Guid savedActivityID = new Guid();
2294 bool etwIsEnabled = log.IsEnabled();
2295 if (etwIsEnabled)
2297 if (log.TasksSetActivityIds)
2298 EventSource.SetCurrentThreadActivityId(TplEventSource.CreateGuidForTaskID(this.Id), out savedActivityID);
2299 // previousTask holds the actual "current task" we want to report in the event
2300 if (previousTask != null)
2301 log.TaskStarted(previousTask.m_taskScheduler!.Id, previousTask.Id, this.Id);
2302 else
2303 log.TaskStarted(TaskScheduler.Current.Id, 0, this.Id);
2306 bool loggingOn = AsyncCausalityTracer.LoggingOn;
2307 if (loggingOn)
2308 AsyncCausalityTracer.TraceSynchronousWorkStart(this, CausalitySynchronousWork.Execution);
2312 // place the current task into TLS.
2313 currentTaskSlot = this;
2315 // Execute the task body
2318 ExecutionContext? ec = CapturedContext;
2319 if (ec == null)
2321 // No context, just run the task directly.
2322 InnerInvoke();
2324 else
2326 // Invoke it under the captured ExecutionContext
2327 if (threadPoolThread is null)
2329 ExecutionContext.RunInternal(ec, s_ecCallback, this);
2331 else
2333 ExecutionContext.RunFromThreadPoolDispatchLoop(threadPoolThread, ec, s_ecCallback, this);
2337 catch (Exception exn)
2339 // Record this exception in the task's exception list
2340 HandleException(exn);
2343 if (loggingOn)
2344 AsyncCausalityTracer.TraceSynchronousWorkCompletion(CausalitySynchronousWork.Execution);
2346 Finish(true);
2348 finally
2350 currentTaskSlot = previousTask;
2352 // ETW event for Task Completed
2353 if (etwIsEnabled)
2355 // previousTask holds the actual "current task" we want to report in the event
2356 if (previousTask != null)
2357 log.TaskCompleted(previousTask.m_taskScheduler!.Id, previousTask.Id, this.Id, IsFaulted);
2358 else
2359 log.TaskCompleted(TaskScheduler.Current.Id, 0, this.Id, IsFaulted);
2361 if (log.TasksSetActivityIds)
2362 EventSource.SetCurrentThreadActivityId(savedActivityID);
2367 private static readonly ContextCallback s_ecCallback = obj =>
2369 Debug.Assert(obj is Task);
2370 // Only used privately to pass directly to EC.Run
2371 Unsafe.As<Task>(obj).InnerInvoke();
2374 /// <summary>
2375 /// The actual code which invokes the body of the task. This can be overridden in derived types.
2376 /// </summary>
2377 internal virtual void InnerInvoke()
2379 // Invoke the delegate
2380 Debug.Assert(m_action != null, "Null action in InnerInvoke()");
2381 if (m_action is Action action)
2383 action();
2384 return;
2387 if (m_action is Action<object?> actionWithState)
2389 actionWithState(m_stateObject);
2390 return;
2392 Debug.Fail("Invalid m_action in Task");
2395 /// <summary>
2396 /// Performs whatever handling is necessary for an unhandled exception. Normally
2397 /// this just entails adding the exception to the holder object.
2398 /// </summary>
2399 /// <param name="unhandledException">The exception that went unhandled.</param>
2400 private void HandleException(Exception unhandledException)
2402 Debug.Assert(unhandledException != null);
2404 if (unhandledException is OperationCanceledException exceptionAsOce && IsCancellationRequested &&
2405 m_contingentProperties!.m_cancellationToken == exceptionAsOce.CancellationToken)
2407 // All conditions are satisfied for us to go into canceled state in Finish().
2408 // Mark the acknowledgement. The exception is also stored to enable it to be
2409 // the exception propagated from an await.
2411 SetCancellationAcknowledged();
2412 AddException(exceptionAsOce, representsCancellation: true);
2414 else
2416 // Other exceptions, including any OCE from the task that doesn't match the tasks' own CT,
2417 // or that gets thrown without the CT being set will be treated as an ordinary exception
2418 // and added to the aggregate.
2420 AddException(unhandledException);
2424 #region Await Support
2425 /// <summary>Gets an awaiter used to await this <see cref="System.Threading.Tasks.Task"/>.</summary>
2426 /// <returns>An awaiter instance.</returns>
2427 /// <remarks>This method is intended for compiler user rather than use directly in code.</remarks>
2428 public TaskAwaiter GetAwaiter()
2430 return new TaskAwaiter(this);
2433 /// <summary>Configures an awaiter used to await this <see cref="System.Threading.Tasks.Task"/>.</summary>
2434 /// <param name="continueOnCapturedContext">
2435 /// true to attempt to marshal the continuation back to the original context captured; otherwise, false.
2436 /// </param>
2437 /// <returns>An object used to await this task.</returns>
2438 public ConfiguredTaskAwaitable ConfigureAwait(bool continueOnCapturedContext)
2440 return new ConfiguredTaskAwaitable(this, continueOnCapturedContext);
2443 /// <summary>
2444 /// Sets a continuation onto the <see cref="System.Threading.Tasks.Task"/>.
2445 /// The continuation is scheduled to run in the current synchronization context is one exists,
2446 /// otherwise in the current task scheduler.
2447 /// </summary>
2448 /// <param name="continuationAction">The action to invoke when the <see cref="System.Threading.Tasks.Task"/> has completed.</param>
2449 /// <param name="continueOnCapturedContext">
2450 /// true to attempt to marshal the continuation back to the original context captured; otherwise, false.
2451 /// </param>
2452 /// <param name="flowExecutionContext">Whether to flow ExecutionContext across the await.</param>
2453 /// <exception cref="System.InvalidOperationException">The awaiter was not properly initialized.</exception>
2454 internal void SetContinuationForAwait(
2455 Action continuationAction, bool continueOnCapturedContext, bool flowExecutionContext)
2457 Debug.Assert(continuationAction != null);
2459 // Create the best AwaitTaskContinuation object given the request.
2460 // If this remains null by the end of the function, we can use the
2461 // continuationAction directly without wrapping it.
2462 TaskContinuation? tc = null;
2464 // If the user wants the continuation to run on the current "context" if there is one...
2465 if (continueOnCapturedContext)
2467 // First try getting the current synchronization context.
2468 // If the current context is really just the base SynchronizationContext type,
2469 // which is intended to be equivalent to not having a current SynchronizationContext at all,
2470 // then ignore it. This helps with performance by avoiding unnecessary posts and queueing
2471 // of work items, but more so it ensures that if code happens to publish the default context
2472 // as current, it won't prevent usage of a current task scheduler if there is one.
2473 SynchronizationContext? syncCtx = SynchronizationContext.Current;
2474 if (syncCtx != null && syncCtx.GetType() != typeof(SynchronizationContext))
2476 tc = new SynchronizationContextAwaitTaskContinuation(syncCtx, continuationAction, flowExecutionContext);
2478 else
2480 // If there was no SynchronizationContext, then try for the current scheduler.
2481 // We only care about it if it's not the default.
2482 TaskScheduler? scheduler = TaskScheduler.InternalCurrent;
2483 if (scheduler != null && scheduler != TaskScheduler.Default)
2485 tc = new TaskSchedulerAwaitTaskContinuation(scheduler, continuationAction, flowExecutionContext);
2490 if (tc == null && flowExecutionContext)
2492 // We're targeting the default scheduler, so we can use the faster path
2493 // that assumes the default, and thus we don't need to store it. If we're flowing
2494 // ExecutionContext, we need to capture it and wrap it in an AwaitTaskContinuation.
2495 // Otherwise, we're targeting the default scheduler and we don't need to flow ExecutionContext, so
2496 // we don't actually need a continuation object. We can just store/queue the action itself.
2497 tc = new AwaitTaskContinuation(continuationAction, flowExecutionContext: true);
2500 // Now register the continuation, and if we couldn't register it because the task is already completing,
2501 // process the continuation directly (in which case make sure we schedule the continuation
2502 // rather than inlining it, the latter of which could result in a rare but possible stack overflow).
2503 if (tc != null)
2505 if (!AddTaskContinuation(tc, addBeforeOthers: false))
2506 tc.Run(this, canInlineContinuationTask: false);
2508 else
2510 Debug.Assert(!flowExecutionContext, "We already determined we're not required to flow context.");
2511 if (!AddTaskContinuation(continuationAction, addBeforeOthers: false))
2512 AwaitTaskContinuation.UnsafeScheduleAction(continuationAction, this);
2516 /// <summary>
2517 /// Sets a continuation onto the <see cref="System.Threading.Tasks.Task"/>.
2518 /// The continuation is scheduled to run in the current synchronization context is one exists,
2519 /// otherwise in the current task scheduler.
2520 /// </summary>
2521 /// <param name="stateMachineBox">The action to invoke when the <see cref="System.Threading.Tasks.Task"/> has completed.</param>
2522 /// <param name="continueOnCapturedContext">
2523 /// true to attempt to marshal the continuation back to the original context captured; otherwise, false.
2524 /// </param>
2525 /// <exception cref="System.InvalidOperationException">The awaiter was not properly initialized.</exception>
2526 internal void UnsafeSetContinuationForAwait(IAsyncStateMachineBox stateMachineBox, bool continueOnCapturedContext)
2528 Debug.Assert(stateMachineBox != null);
2530 // This code path doesn't emit all expected TPL-related events, such as for continuations.
2531 // It's expected that all callers check whether events are enabled before calling this function,
2532 // and only call it if they're not, so we assert. However, as events can be dynamically turned
2533 // on and off, it's possible this assert could fire even when used correctly. If it becomes
2534 // noisy, it can be deleted.
2535 Debug.Assert(!TplEventSource.Log.IsEnabled());
2537 // If the caller wants to continue on the current context/scheduler and there is one,
2538 // fall back to using the state machine's delegate.
2539 if (continueOnCapturedContext)
2541 SynchronizationContext? syncCtx = SynchronizationContext.Current;
2542 if (syncCtx != null && syncCtx.GetType() != typeof(SynchronizationContext))
2544 var tc = new SynchronizationContextAwaitTaskContinuation(syncCtx, stateMachineBox.MoveNextAction, flowExecutionContext: false);
2545 if (!AddTaskContinuation(tc, addBeforeOthers: false))
2547 tc.Run(this, canInlineContinuationTask: false);
2549 return;
2551 else
2553 TaskScheduler? scheduler = TaskScheduler.InternalCurrent;
2554 if (scheduler != null && scheduler != TaskScheduler.Default)
2556 var tc = new TaskSchedulerAwaitTaskContinuation(scheduler, stateMachineBox.MoveNextAction, flowExecutionContext: false);
2557 if (!AddTaskContinuation(tc, addBeforeOthers: false))
2559 tc.Run(this, canInlineContinuationTask: false);
2561 return;
2566 // Otherwise, add the state machine box directly as the continuation.
2567 // If we're unable to because the task has already completed, queue it.
2568 if (!AddTaskContinuation(stateMachineBox, addBeforeOthers: false))
2570 Debug.Assert(stateMachineBox is Task, "Every state machine box should derive from Task");
2571 ThreadPool.UnsafeQueueUserWorkItemInternal(stateMachineBox, preferLocal: true);
2575 /// <summary>Creates an awaitable that asynchronously yields back to the current context when awaited.</summary>
2576 /// <returns>
2577 /// A context that, when awaited, will asynchronously transition back into the current context at the
2578 /// time of the await. If the current SynchronizationContext is non-null, that is treated as the current context.
2579 /// Otherwise, TaskScheduler.Current is treated as the current context.
2580 /// </returns>
2581 public static YieldAwaitable Yield()
2583 return new YieldAwaitable();
2585 #endregion
2587 /// <summary>
2588 /// Waits for the <see cref="Task"/> to complete execution.
2589 /// </summary>
2590 /// <exception cref="System.AggregateException">
2591 /// The <see cref="Task"/> was canceled -or- an exception was thrown during
2592 /// the execution of the <see cref="Task"/>.
2593 /// </exception>
2594 public void Wait()
2596 #if DEBUG
2597 bool waitResult =
2598 #endif
2599 Wait(Timeout.Infinite, default);
2601 #if DEBUG
2602 Debug.Assert(waitResult, "expected wait to succeed");
2603 #endif
2606 /// <summary>
2607 /// Waits for the <see cref="Task"/> to complete execution.
2608 /// </summary>
2609 /// <param name="timeout">
2610 /// A <see cref="System.TimeSpan"/> that represents the number of milliseconds to wait, or a <see
2611 /// cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely.
2612 /// </param>
2613 /// <returns>
2614 /// true if the <see cref="Task"/> completed execution within the allotted time; otherwise, false.
2615 /// </returns>
2616 /// <exception cref="System.AggregateException">
2617 /// The <see cref="Task"/> was canceled -or- an exception was thrown during the execution of the <see
2618 /// cref="Task"/>.
2619 /// </exception>
2620 /// <exception cref="System.ArgumentOutOfRangeException">
2621 /// <paramref name="timeout"/> is a negative number other than -1 milliseconds, which represents an
2622 /// infinite time-out -or- timeout is greater than
2623 /// <see cref="int.MaxValue"/>.
2624 /// </exception>
2625 public bool Wait(TimeSpan timeout)
2627 long totalMilliseconds = (long)timeout.TotalMilliseconds;
2628 if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue)
2630 ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.timeout);
2633 return Wait((int)totalMilliseconds, default);
2637 /// <summary>
2638 /// Waits for the <see cref="Task"/> to complete execution.
2639 /// </summary>
2640 /// <param name="cancellationToken">
2641 /// A <see cref="CancellationToken"/> to observe while waiting for the task to complete.
2642 /// </param>
2643 /// <exception cref="System.OperationCanceledException">
2644 /// The <paramref name="cancellationToken"/> was canceled.
2645 /// </exception>
2646 /// <exception cref="System.AggregateException">
2647 /// The <see cref="Task"/> was canceled -or- an exception was thrown during the execution of the <see
2648 /// cref="Task"/>.
2649 /// </exception>
2650 public void Wait(CancellationToken cancellationToken)
2652 Wait(Timeout.Infinite, cancellationToken);
2656 /// <summary>
2657 /// Waits for the <see cref="Task"/> to complete execution.
2658 /// </summary>
2659 /// <param name="millisecondsTimeout">
2660 /// The number of milliseconds to wait, or <see cref="System.Threading.Timeout.Infinite"/> (-1) to
2661 /// wait indefinitely.</param>
2662 /// <returns>true if the <see cref="Task"/> completed execution within the allotted time; otherwise,
2663 /// false.
2664 /// </returns>
2665 /// <exception cref="System.ArgumentOutOfRangeException">
2666 /// <paramref name="millisecondsTimeout"/> is a negative number other than -1, which represents an
2667 /// infinite time-out.
2668 /// </exception>
2669 /// <exception cref="System.AggregateException">
2670 /// The <see cref="Task"/> was canceled -or- an exception was thrown during the execution of the <see
2671 /// cref="Task"/>.
2672 /// </exception>
2673 public bool Wait(int millisecondsTimeout)
2675 return Wait(millisecondsTimeout, default);
2679 /// <summary>
2680 /// Waits for the <see cref="Task"/> to complete execution.
2681 /// </summary>
2682 /// <param name="millisecondsTimeout">
2683 /// The number of milliseconds to wait, or <see cref="System.Threading.Timeout.Infinite"/> (-1) to
2684 /// wait indefinitely.
2685 /// </param>
2686 /// <param name="cancellationToken">
2687 /// A <see cref="CancellationToken"/> to observe while waiting for the task to complete.
2688 /// </param>
2689 /// <returns>
2690 /// true if the <see cref="Task"/> completed execution within the allotted time; otherwise, false.
2691 /// </returns>
2692 /// <exception cref="System.AggregateException">
2693 /// The <see cref="Task"/> was canceled -or- an exception was thrown during the execution of the <see
2694 /// cref="Task"/>.
2695 /// </exception>
2696 /// <exception cref="System.ArgumentOutOfRangeException">
2697 /// <paramref name="millisecondsTimeout"/> is a negative number other than -1, which represents an
2698 /// infinite time-out.
2699 /// </exception>
2700 /// <exception cref="System.OperationCanceledException">
2701 /// The <paramref name="cancellationToken"/> was canceled.
2702 /// </exception>
2703 public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken)
2705 if (millisecondsTimeout < -1)
2707 ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.millisecondsTimeout);
2710 // Return immediately if we know that we've completed "clean" -- no exceptions, no cancellations
2711 // and if no notification to the debugger is required
2712 if (!IsWaitNotificationEnabledOrNotRanToCompletion) // (!DebuggerBitSet && RanToCompletion)
2713 return true;
2715 // Wait, and then return if we're still not done.
2716 if (!InternalWait(millisecondsTimeout, cancellationToken))
2717 return false;
2719 if (IsWaitNotificationEnabledOrNotRanToCompletion) // avoid a few unnecessary volatile reads if we completed successfully
2721 // Notify the debugger of the wait completion if it's requested such a notification
2722 NotifyDebuggerOfWaitCompletionIfNecessary();
2724 // If cancellation was requested and the task was canceled, throw an
2725 // OperationCanceledException. This is prioritized ahead of the ThrowIfExceptional
2726 // call to bring more determinism to cases where the same token is used to
2727 // cancel the Wait and to cancel the Task. Otherwise, there's a race condition between
2728 // whether the Wait or the Task observes the cancellation request first,
2729 // and different exceptions result from the different cases.
2730 if (IsCanceled) cancellationToken.ThrowIfCancellationRequested();
2732 // If an exception occurred, or the task was cancelled, throw an exception.
2733 ThrowIfExceptional(true);
2736 Debug.Assert((m_stateFlags & TASK_STATE_FAULTED) == 0, "Task.Wait() completing when in Faulted state.");
2738 return true;
2741 // Convenience method that wraps any scheduler exception in a TaskSchedulerException
2742 // and rethrows it.
2743 private bool WrappedTryRunInline()
2745 if (m_taskScheduler == null)
2746 return false;
2750 return m_taskScheduler.TryRunInline(this, true);
2752 catch (Exception e)
2754 throw new TaskSchedulerException(e);
2758 /// <summary>
2759 /// The core wait function, which is only accessible internally. It's meant to be used in places in TPL code where
2760 /// the current context is known or cached.
2761 /// </summary>
2762 [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
2763 internal bool InternalWait(int millisecondsTimeout, CancellationToken cancellationToken) =>
2764 InternalWaitCore(millisecondsTimeout, cancellationToken);
2766 // Separated out to allow it to be optimized (caller is marked NoOptimization for VS parallel debugger
2767 // to be able to see the method on the stack and inspect arguments).
2768 private bool InternalWaitCore(int millisecondsTimeout, CancellationToken cancellationToken)
2770 // If the task has already completed, there's nothing to wait for.
2771 bool returnValue = IsCompleted;
2772 if (returnValue)
2774 return true;
2777 // ETW event for Task Wait Begin
2778 TplEventSource log = TplEventSource.Log;
2779 bool etwIsEnabled = log.IsEnabled();
2780 if (etwIsEnabled)
2782 Task? currentTask = Task.InternalCurrent;
2783 log.TaskWaitBegin(
2784 (currentTask != null ? currentTask.m_taskScheduler!.Id : TaskScheduler.Default.Id), (currentTask != null ? currentTask.Id : 0),
2785 this.Id, TplEventSource.TaskWaitBehavior.Synchronous, 0);
2788 // Alert a listening debugger that we can't make forward progress unless it slips threads.
2789 // We call NOCTD for two reasons:
2790 // 1. If the task runs on another thread, then we'll be blocked here indefinitely.
2791 // 2. If the task runs inline but takes some time to complete, it will suffer ThreadAbort with possible state corruption,
2792 // and it is best to prevent this unless the user explicitly asks to view the value with thread-slipping enabled.
2793 Debugger.NotifyOfCrossThreadDependency();
2795 // We will attempt inline execution only if an infinite wait was requested
2796 // Inline execution doesn't make sense for finite timeouts and if a cancellation token was specified
2797 // because we don't know how long the task delegate will take.
2798 if (millisecondsTimeout == Timeout.Infinite && !cancellationToken.CanBeCanceled &&
2799 WrappedTryRunInline() && IsCompleted) // TryRunInline doesn't guarantee completion, as there may be unfinished children.
2801 returnValue = true;
2803 else
2805 returnValue = SpinThenBlockingWait(millisecondsTimeout, cancellationToken);
2808 Debug.Assert(IsCompleted || millisecondsTimeout != Timeout.Infinite);
2810 // ETW event for Task Wait End
2811 if (etwIsEnabled)
2813 Task? currentTask = Task.InternalCurrent;
2814 if (currentTask != null)
2816 log.TaskWaitEnd(currentTask.m_taskScheduler!.Id, currentTask.Id, this.Id);
2818 else
2820 log.TaskWaitEnd(TaskScheduler.Default.Id, 0, this.Id);
2822 // logically the continuation is empty so we immediately fire
2823 log.TaskWaitContinuationComplete(this.Id);
2826 return returnValue;
2829 // An MRES that gets set when Invoke is called. This replaces old logic that looked like this:
2830 // ManualResetEventSlim mres = new ManualResetEventSlim(false, 0);
2831 // Action<Task> completionAction = delegate {mres.Set();}
2832 // AddCompletionAction(completionAction);
2833 // with this:
2834 // SetOnInvokeMres mres = new SetOnInvokeMres();
2835 // AddCompletionAction(mres, addBeforeOthers: true);
2836 // which saves a couple of allocations.
2838 // Used in SpinThenBlockingWait (below), but could be seen as a general purpose mechanism.
2839 private sealed class SetOnInvokeMres : ManualResetEventSlim, ITaskCompletionAction
2841 internal SetOnInvokeMres() : base(false, 0) { }
2842 public void Invoke(Task completingTask) { Set(); }
2843 public bool InvokeMayRunArbitraryCode => false;
2846 /// <summary>
2847 /// Waits for the task to complete, for a timeout to occur, or for cancellation to be requested.
2848 /// The method first spins and then falls back to blocking on a new event.
2849 /// </summary>
2850 /// <param name="millisecondsTimeout">The timeout.</param>
2851 /// <param name="cancellationToken">The token.</param>
2852 /// <returns>true if the task is completed; otherwise, false.</returns>
2853 private bool SpinThenBlockingWait(int millisecondsTimeout, CancellationToken cancellationToken)
2855 bool infiniteWait = millisecondsTimeout == Timeout.Infinite;
2856 uint startTimeTicks = infiniteWait ? 0 : (uint)Environment.TickCount;
2857 bool returnValue = SpinWait(millisecondsTimeout);
2858 if (!returnValue)
2860 var mres = new SetOnInvokeMres();
2863 AddCompletionAction(mres, addBeforeOthers: true);
2864 if (infiniteWait)
2866 returnValue = mres.Wait(Timeout.Infinite, cancellationToken);
2868 else
2870 uint elapsedTimeTicks = ((uint)Environment.TickCount) - startTimeTicks;
2871 if (elapsedTimeTicks < millisecondsTimeout)
2873 returnValue = mres.Wait((int)(millisecondsTimeout - elapsedTimeTicks), cancellationToken);
2877 finally
2879 if (!IsCompleted) RemoveContinuation(mres);
2880 // Don't Dispose of the MRES, because the continuation off of this task may
2881 // still be running. This is ok, however, as we never access the MRES' WaitHandle,
2882 // and thus no finalizable resources are actually allocated.
2885 return returnValue;
2888 /// <summary>
2889 /// Spins briefly while checking IsCompleted
2890 /// </summary>
2891 /// <param name="millisecondsTimeout">The timeout.</param>
2892 /// <returns>true if the task is completed; otherwise, false.</returns>
2893 /// <exception cref="System.OperationCanceledException">The wait was canceled.</exception>
2894 private bool SpinWait(int millisecondsTimeout)
2896 if (IsCompleted) return true;
2898 if (millisecondsTimeout == 0)
2900 // For 0-timeouts, we just return immediately.
2901 return false;
2904 int spinCount = Threading.SpinWait.SpinCountforSpinBeforeWait;
2905 var spinner = new SpinWait();
2906 while (spinner.Count < spinCount)
2908 spinner.SpinOnce(sleep1Threshold: -1);
2910 if (IsCompleted)
2912 return true;
2916 return false;
2919 /// <summary>
2920 /// Cancels the <see cref="Task"/>.
2921 /// </summary>
2922 /// <param name="bCancelNonExecutingOnly">
2923 /// Indicates whether we should only cancel non-invoked tasks.
2924 /// For the default scheduler this option will only be serviced through TryDequeue.
2925 /// For custom schedulers we also attempt an atomic state transition.
2926 /// </param>
2927 /// <returns>true if the task was successfully canceled; otherwise, false.</returns>
2928 internal bool InternalCancel(bool bCancelNonExecutingOnly)
2930 Debug.Assert((Options & (TaskCreationOptions)InternalTaskOptions.PromiseTask) == 0, "Task.InternalCancel() did not expect promise-style task");
2932 bool bPopSucceeded = false;
2933 bool mustCleanup = false;
2935 TaskSchedulerException? tse = null;
2937 // If started, and running in a task context, we can try to pop the chore.
2938 if ((m_stateFlags & TASK_STATE_STARTED) != 0)
2940 TaskScheduler? ts = m_taskScheduler;
2944 bPopSucceeded = (ts != null) && ts.TryDequeue(this);
2946 catch (Exception e)
2948 // TryDequeue threw. We don't know whether the task was properly dequeued or not. So we must let the rest of
2949 // the cancellation logic run its course (record the request, attempt atomic state transition and do cleanup where appropriate)
2950 // Here we will only record a TaskSchedulerException, which will later be thrown at function exit.
2952 tse = new TaskSchedulerException(e);
2955 bool bRequiresAtomicStartTransition = ts != null && ts.RequiresAtomicStartTransition;
2957 if (!bPopSucceeded && bCancelNonExecutingOnly && bRequiresAtomicStartTransition)
2959 // The caller requested cancellation of non-invoked tasks only, and TryDequeue was one way of doing it...
2960 // Since that seems to have failed, we should now try an atomic state transition (from non-invoked state to canceled)
2961 // An atomic transition here is only safe if we know we're on a custom task scheduler, which also forces a CAS on ExecuteEntry
2963 // Even though this task can't have any children, we should be ready for handling any continuations that
2964 // may be attached to it (although currently
2965 // So we need to remeber whether we actually did the flip, so we can do clean up (finish continuations etc)
2966 mustCleanup = AtomicStateUpdate(TASK_STATE_CANCELED, TASK_STATE_DELEGATE_INVOKED | TASK_STATE_CANCELED);
2968 // PS: This is slightly different from the regular cancellation codepath
2969 // since we record the cancellation request *after* doing the state transition.
2970 // However that shouldn't matter too much because the task was never invoked, thus can't have children
2974 if (!bCancelNonExecutingOnly || bPopSucceeded || mustCleanup)
2976 // Record the cancellation request.
2977 RecordInternalCancellationRequest();
2979 // Determine whether we need to clean up
2980 // This will be the case
2981 // 1) if we were able to pop, and we are able to update task state to TASK_STATE_CANCELED
2982 // 2) if the task seems to be yet unstarted, and we can transition to
2983 // TASK_STATE_CANCELED before anyone else can transition into _STARTED or _CANCELED or
2984 // _RAN_TO_COMPLETION or _FAULTED
2985 // Note that we do not check for TASK_STATE_COMPLETION_RESERVED. That only applies to promise-style
2986 // tasks, and a promise-style task should not enter into this codepath.
2987 if (bPopSucceeded)
2989 // hitting this would mean something wrong with the AtomicStateUpdate above
2990 Debug.Assert(!mustCleanup, "Possibly an invalid state transition call was made in InternalCancel()");
2992 // Include TASK_STATE_DELEGATE_INVOKED in "illegal" bits to protect against the situation where
2993 // TS.TryDequeue() returns true but the task is still left on the queue.
2994 mustCleanup = AtomicStateUpdate(TASK_STATE_CANCELED, TASK_STATE_CANCELED | TASK_STATE_DELEGATE_INVOKED);
2996 else if (!mustCleanup && (m_stateFlags & TASK_STATE_STARTED) == 0)
2998 mustCleanup = AtomicStateUpdate(TASK_STATE_CANCELED,
2999 TASK_STATE_CANCELED | TASK_STATE_STARTED | TASK_STATE_RAN_TO_COMPLETION |
3000 TASK_STATE_FAULTED | TASK_STATE_DELEGATE_INVOKED);
3003 // do the cleanup (i.e. set completion event and finish continuations)
3004 if (mustCleanup)
3006 CancellationCleanupLogic();
3010 if (tse != null)
3011 throw tse;
3012 else
3013 return (mustCleanup);
3016 // Breaks out logic for recording a cancellation request
3017 internal void RecordInternalCancellationRequest()
3019 // Record the cancellation request.
3020 EnsureContingentPropertiesInitialized().m_internalCancellationRequested = CANCELLATION_REQUESTED;
3023 // Breaks out logic for recording a cancellation request
3024 // This overload should only be used for promise tasks where no cancellation token
3025 // was supplied when the task was created.
3026 internal void RecordInternalCancellationRequest(CancellationToken tokenToRecord)
3028 RecordInternalCancellationRequest();
3030 Debug.Assert((Options & (TaskCreationOptions)InternalTaskOptions.PromiseTask) != 0, "Task.RecordInternalCancellationRequest(CancellationToken) only valid for promise-style task");
3031 Debug.Assert(m_contingentProperties!.m_cancellationToken == default);
3033 // Store the supplied cancellation token as this task's token.
3034 // Waiting on this task will then result in an OperationCanceledException containing this token.
3035 if (tokenToRecord != default)
3037 m_contingentProperties.m_cancellationToken = tokenToRecord;
3041 // Breaks out logic for recording a cancellation request
3042 // This overload should only be used for promise tasks where no cancellation token
3043 // was supplied when the task was created.
3044 internal void RecordInternalCancellationRequest(CancellationToken tokenToRecord, object? cancellationException)
3046 RecordInternalCancellationRequest(tokenToRecord);
3048 // Store the supplied cancellation exception
3049 if (cancellationException != null)
3051 #if DEBUG
3052 var oce = cancellationException as OperationCanceledException;
3053 if (oce == null)
3055 var edi = cancellationException as ExceptionDispatchInfo;
3056 Debug.Assert(edi != null, "Expected either an OCE or an EDI");
3057 oce = edi.SourceException as OperationCanceledException;
3058 Debug.Assert(oce != null, "Expected EDI to contain an OCE");
3060 Debug.Assert(oce.CancellationToken == tokenToRecord,
3061 "Expected OCE's token to match the provided token.");
3062 #endif
3063 AddException(cancellationException, representsCancellation: true);
3067 // ASSUMES THAT A SUCCESSFUL CANCELLATION HAS JUST OCCURRED ON THIS TASK!!!
3068 // And this method should be called at most once per task.
3069 internal void CancellationCleanupLogic()
3071 Debug.Assert((m_stateFlags & (TASK_STATE_CANCELED | TASK_STATE_COMPLETION_RESERVED)) != 0, "Task.CancellationCleanupLogic(): Task not canceled or reserved.");
3072 // I'd like to do this, but there is a small window for a race condition. If someone calls Wait() between InternalCancel() and
3073 // here, that will set m_completionEvent, leading to a meaningless/harmless assertion.
3074 //Debug.Assert((m_completionEvent == null) || !m_completionEvent.IsSet, "Task.CancellationCleanupLogic(): Completion event already set.");
3076 // This may have been set already, but we need to make sure.
3077 Interlocked.Exchange(ref m_stateFlags, m_stateFlags | TASK_STATE_CANCELED);
3079 // Fire completion event if it has been lazily initialized
3080 ContingentProperties? cp = Volatile.Read(ref m_contingentProperties);
3081 if (cp != null)
3083 cp.SetCompleted();
3084 cp.UnregisterCancellationCallback();
3087 if (AsyncCausalityTracer.LoggingOn)
3088 AsyncCausalityTracer.TraceOperationCompletion(this, AsyncCausalityStatus.Canceled);
3090 if (s_asyncDebuggingEnabled)
3091 RemoveFromActiveTasks(this);
3093 // Notify parents, fire continuations, other cleanup.
3094 FinishStageThree();
3098 /// <summary>
3099 /// Sets the task's cancellation acknowledged flag.
3100 /// </summary>
3101 private void SetCancellationAcknowledged()
3103 Debug.Assert(this == Task.InternalCurrent, "SetCancellationAcknowledged() should only be called while this is still the current task");
3104 Debug.Assert(IsCancellationRequested, "SetCancellationAcknowledged() should not be called if the task's CT wasn't signaled");
3106 m_stateFlags |= TASK_STATE_CANCELLATIONACKNOWLEDGED;
3109 /// <summary>Completes a promise task as RanToCompletion.</summary>
3110 /// <remarks>If this is a Task{T}, default(T) is the implied result.</remarks>
3111 /// <returns>true if the task was transitioned to ran to completion; false if it was already completed.</returns>
3112 internal bool TrySetResult()
3114 Debug.Assert(m_action == null, "Task<T>.TrySetResult(): non-null m_action");
3116 if (AtomicStateUpdate(
3117 TASK_STATE_COMPLETION_RESERVED | TASK_STATE_RAN_TO_COMPLETION,
3118 TASK_STATE_COMPLETION_RESERVED | TASK_STATE_RAN_TO_COMPLETION | TASK_STATE_FAULTED | TASK_STATE_CANCELED))
3120 ContingentProperties? props = m_contingentProperties;
3121 if (props != null)
3123 NotifyParentIfPotentiallyAttachedTask();
3124 props.SetCompleted();
3126 FinishContinuations();
3127 return true;
3130 return false;
3133 // Allow multiple exceptions to be assigned to a promise-style task.
3134 // This is useful when a TaskCompletionSource<T> stands in as a proxy
3135 // for a "real" task (as we do in Unwrap(), ContinueWhenAny() and ContinueWhenAll())
3136 // and the "real" task ends up with multiple exceptions, which is possible when
3137 // a task has children.
3139 // Called from TaskCompletionSource<T>.SetException(IEnumerable<Exception>).
3140 internal bool TrySetException(object exceptionObject)
3142 Debug.Assert(m_action == null, "Task<T>.TrySetException(): non-null m_action");
3144 // TCS.{Try}SetException() should have checked for this
3145 Debug.Assert(exceptionObject != null, "Expected non-null exceptionObject argument");
3147 // Only accept these types.
3148 Debug.Assert(
3149 (exceptionObject is Exception) || (exceptionObject is IEnumerable<Exception>) ||
3150 (exceptionObject is ExceptionDispatchInfo) || (exceptionObject is IEnumerable<ExceptionDispatchInfo>),
3151 "Expected exceptionObject to be either Exception, ExceptionDispatchInfo, or IEnumerable<> of one of those");
3153 bool returnValue = false;
3155 // "Reserve" the completion for this task, while making sure that: (1) No prior reservation
3156 // has been made, (2) The result has not already been set, (3) An exception has not previously
3157 // been recorded, and (4) Cancellation has not been requested.
3159 // If the reservation is successful, then add the exception(s) and finish completion processing.
3161 // The lazy initialization may not be strictly necessary, but I'd like to keep it here
3162 // anyway. Some downstream logic may depend upon an inflated m_contingentProperties.
3163 EnsureContingentPropertiesInitialized();
3164 if (AtomicStateUpdate(
3165 TASK_STATE_COMPLETION_RESERVED,
3166 TASK_STATE_COMPLETION_RESERVED | TASK_STATE_RAN_TO_COMPLETION | TASK_STATE_FAULTED | TASK_STATE_CANCELED))
3168 AddException(exceptionObject); // handles singleton exception or exception collection
3169 Finish(false);
3170 returnValue = true;
3173 return returnValue;
3176 // internal helper function breaks out logic used by TaskCompletionSource and AsyncMethodBuilder
3177 // If the tokenToRecord is not None, it will be stored onto the task.
3178 // This method is only valid for promise tasks.
3179 internal bool TrySetCanceled(CancellationToken tokenToRecord)
3181 return TrySetCanceled(tokenToRecord, null);
3184 // internal helper function breaks out logic used by TaskCompletionSource and AsyncMethodBuilder
3185 // If the tokenToRecord is not None, it will be stored onto the task.
3186 // If the OperationCanceledException is not null, it will be stored into the task's exception holder.
3187 // This method is only valid for promise tasks.
3188 internal bool TrySetCanceled(CancellationToken tokenToRecord, object? cancellationException)
3190 Debug.Assert(m_action == null, "Task<T>.TrySetCanceled(): non-null m_action");
3191 Debug.Assert(
3192 cancellationException == null ||
3193 cancellationException is OperationCanceledException ||
3194 (cancellationException as ExceptionDispatchInfo)?.SourceException is OperationCanceledException,
3195 "Expected null or an OperationCanceledException");
3197 bool returnValue = false;
3199 // "Reserve" the completion for this task, while making sure that: (1) No prior reservation
3200 // has been made, (2) The result has not already been set, (3) An exception has not previously
3201 // been recorded, and (4) Cancellation has not been requested.
3203 // If the reservation is successful, then record the cancellation and finish completion processing.
3204 if (AtomicStateUpdate(
3205 TASK_STATE_COMPLETION_RESERVED,
3206 TASK_STATE_COMPLETION_RESERVED | TASK_STATE_CANCELED | TASK_STATE_FAULTED | TASK_STATE_RAN_TO_COMPLETION))
3208 RecordInternalCancellationRequest(tokenToRecord, cancellationException);
3209 CancellationCleanupLogic(); // perform cancellation cleanup actions
3210 returnValue = true;
3213 return returnValue;
3218 // Continuation passing functionality (aka ContinueWith)
3224 /// <summary>
3225 /// Runs all of the continuations, as appropriate.
3226 /// </summary>
3227 internal void FinishContinuations()
3229 // Atomically store the fact that this task is completing. From this point on, the adding of continuations will
3230 // result in the continuations being run/launched directly rather than being added to the continuation list.
3231 // Then if we grabbed any continuations, run them.
3232 object? continuationObject = Interlocked.Exchange(ref m_continuationObject, s_taskCompletionSentinel);
3233 if (continuationObject != null)
3235 RunContinuations(continuationObject);
3239 private void RunContinuations(object continuationObject) // separated out of FinishContinuations to enable it to be inlined
3241 Debug.Assert(continuationObject != null);
3243 TplEventSource? log = TplEventSource.Log;
3244 if (!log.IsEnabled())
3246 log = null;
3249 if (AsyncCausalityTracer.LoggingOn)
3250 AsyncCausalityTracer.TraceSynchronousWorkStart(this, CausalitySynchronousWork.CompletionNotification);
3252 bool canInlineContinuations =
3253 (m_stateFlags & (int)TaskCreationOptions.RunContinuationsAsynchronously) == 0 &&
3254 RuntimeHelpers.TryEnsureSufficientExecutionStack();
3256 switch (continuationObject)
3258 // Handle the single IAsyncStateMachineBox case. This could be handled as part of the ITaskCompletionAction
3259 // but we want to ensure that inlining is properly handled in the face of schedulers, so its behavior
3260 // needs to be customized ala raw Actions. This is also the most important case, as it represents the
3261 // most common form of continuation, so we check it first.
3262 case IAsyncStateMachineBox stateMachineBox:
3263 AwaitTaskContinuation.RunOrScheduleAction(stateMachineBox, canInlineContinuations);
3264 LogFinishCompletionNotification();
3265 return;
3267 // Handle the single Action case.
3268 case Action action:
3269 AwaitTaskContinuation.RunOrScheduleAction(action, canInlineContinuations);
3270 LogFinishCompletionNotification();
3271 return;
3273 // Handle the single TaskContinuation case.
3274 case TaskContinuation tc:
3275 tc.Run(this, canInlineContinuations);
3276 LogFinishCompletionNotification();
3277 return;
3279 // Handle the single ITaskCompletionAction case.
3280 case ITaskCompletionAction completionAction:
3281 RunOrQueueCompletionAction(completionAction, canInlineContinuations);
3282 LogFinishCompletionNotification();
3283 return;
3286 // Not a single; it must be a list.
3287 List<object?> continuations = (List<object?>)continuationObject;
3290 // Begin processing of continuation list
3293 // Wait for any concurrent adds or removes to be retired
3294 lock (continuations) { }
3295 int continuationCount = continuations.Count;
3297 // Fire the asynchronous continuations first. However, if we're not able to run any continuations synchronously,
3298 // then we can skip this first pass, since the second pass that tries to run everything synchronously will instead
3299 // run everything asynchronously anyway.
3300 if (canInlineContinuations)
3302 bool forceContinuationsAsync = false;
3303 for (int i = 0; i < continuationCount; i++)
3305 // For StandardTaskContinuations, we respect the TaskContinuationOptions.ExecuteSynchronously option,
3306 // as the developer needs to explicitly opt-into running the continuation synchronously, and if they do,
3307 // they get what they asked for. ITaskCompletionActions are only ever created by the runtime, and we always
3308 // try to execute them synchronously. For all other continuations (related to await), we only run it synchronously
3309 // if it's the first such continuation; otherwise, we force it to run asynchronously so as to not artificially
3310 // delay an await continuation behind other arbitrary user code created as a previous await continuation.
3312 object? currentContinuation = continuations[i];
3313 if (currentContinuation == null)
3315 // The continuation was unregistered and null'd out, so just skip it.
3316 continue;
3318 else if (currentContinuation is StandardTaskContinuation stc)
3320 if ((stc.m_options & TaskContinuationOptions.ExecuteSynchronously) == 0)
3322 continuations[i] = null; // so that we can skip this later
3323 log?.RunningContinuationList(Id, i, stc);
3324 stc.Run(this, canInlineContinuationTask: false);
3327 else if (!(currentContinuation is ITaskCompletionAction))
3329 if (forceContinuationsAsync)
3331 continuations[i] = null;
3332 log?.RunningContinuationList(Id, i, currentContinuation);
3333 switch (currentContinuation)
3335 case IAsyncStateMachineBox stateMachineBox:
3336 AwaitTaskContinuation.RunOrScheduleAction(stateMachineBox, allowInlining: false);
3337 break;
3339 case Action action:
3340 AwaitTaskContinuation.RunOrScheduleAction(action, allowInlining: false);
3341 break;
3343 default:
3344 Debug.Assert(currentContinuation is TaskContinuation);
3345 ((TaskContinuation)currentContinuation).Run(this, canInlineContinuationTask: false);
3346 break;
3349 forceContinuationsAsync = true;
3354 // ... and then fire the synchronous continuations (if there are any).
3355 for (int i = 0; i < continuationCount; i++)
3357 object? currentContinuation = continuations[i];
3358 if (currentContinuation == null)
3360 continue;
3362 continuations[i] = null; // to enable free'ing up memory earlier
3363 log?.RunningContinuationList(Id, i, currentContinuation);
3365 switch (currentContinuation)
3367 case IAsyncStateMachineBox stateMachineBox:
3368 AwaitTaskContinuation.RunOrScheduleAction(stateMachineBox, canInlineContinuations);
3369 break;
3371 case Action action:
3372 AwaitTaskContinuation.RunOrScheduleAction(action, canInlineContinuations);
3373 break;
3375 case TaskContinuation tc:
3376 tc.Run(this, canInlineContinuations);
3377 break;
3379 default:
3380 Debug.Assert(currentContinuation is ITaskCompletionAction);
3381 RunOrQueueCompletionAction((ITaskCompletionAction)currentContinuation, canInlineContinuations);
3382 break;
3386 LogFinishCompletionNotification();
3389 private void RunOrQueueCompletionAction(ITaskCompletionAction completionAction, bool allowInlining)
3391 if (allowInlining || !completionAction.InvokeMayRunArbitraryCode)
3393 completionAction.Invoke(this);
3395 else
3397 ThreadPool.UnsafeQueueUserWorkItemInternal(new CompletionActionInvoker(completionAction, this), preferLocal: true);
3401 private static void LogFinishCompletionNotification()
3403 if (AsyncCausalityTracer.LoggingOn)
3404 AsyncCausalityTracer.TraceSynchronousWorkCompletion(CausalitySynchronousWork.CompletionNotification);
3407 #region Continuation methods
3409 #region Action<Task> continuation
3410 /// <summary>
3411 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
3412 /// </summary>
3413 /// <param name="continuationAction">
3414 /// An action to run when the <see cref="Task"/> completes. When run, the delegate will be
3415 /// passed the completed task as an argument.
3416 /// </param>
3417 /// <returns>A new continuation <see cref="Task"/>.</returns>
3418 /// <remarks>
3419 /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has
3420 /// completed, whether it completes due to running to completion successfully, faulting due to an
3421 /// unhandled exception, or exiting out early due to being canceled.
3422 /// </remarks>
3423 /// <exception cref="System.ArgumentNullException">
3424 /// The <paramref name="continuationAction"/> argument is null.
3425 /// </exception>
3426 public Task ContinueWith(Action<Task> continuationAction)
3428 return ContinueWith(continuationAction, TaskScheduler.Current, default, TaskContinuationOptions.None);
3431 /// <summary>
3432 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
3433 /// </summary>
3434 /// <param name="continuationAction">
3435 /// An action to run when the <see cref="Task"/> completes. When run, the delegate will be
3436 /// passed the completed task as an argument.
3437 /// </param>
3438 /// <param name="cancellationToken"> The <see cref="CancellationToken"/> that will be assigned to the new continuation task.</param>
3439 /// <returns>A new continuation <see cref="Task"/>.</returns>
3440 /// <remarks>
3441 /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has
3442 /// completed, whether it completes due to running to completion successfully, faulting due to an
3443 /// unhandled exception, or exiting out early due to being canceled.
3444 /// </remarks>
3445 /// <exception cref="System.ArgumentNullException">
3446 /// The <paramref name="continuationAction"/> argument is null.
3447 /// </exception>
3448 /// <exception cref="System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see>
3449 /// has already been disposed.
3450 /// </exception>
3451 public Task ContinueWith(Action<Task> continuationAction, CancellationToken cancellationToken)
3453 return ContinueWith(continuationAction, TaskScheduler.Current, cancellationToken, TaskContinuationOptions.None);
3456 /// <summary>
3457 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
3458 /// </summary>
3459 /// <param name="continuationAction">
3460 /// An action to run when the <see cref="Task"/> completes. When run, the delegate will be
3461 /// passed the completed task as an argument.
3462 /// </param>
3463 /// <param name="scheduler">
3464 /// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its execution.
3465 /// </param>
3466 /// <returns>A new continuation <see cref="Task"/>.</returns>
3467 /// <remarks>
3468 /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has
3469 /// completed, whether it completes due to running to completion successfully, faulting due to an
3470 /// unhandled exception, or exiting out early due to being canceled.
3471 /// </remarks>
3472 /// <exception cref="System.ArgumentNullException">
3473 /// The <paramref name="continuationAction"/> argument is null.
3474 /// </exception>
3475 /// <exception cref="System.ArgumentNullException">
3476 /// The <paramref name="scheduler"/> argument is null.
3477 /// </exception>
3478 public Task ContinueWith(Action<Task> continuationAction, TaskScheduler scheduler)
3480 return ContinueWith(continuationAction, scheduler, default, TaskContinuationOptions.None);
3483 /// <summary>
3484 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
3485 /// </summary>
3486 /// <param name="continuationAction">
3487 /// An action to run when the <see cref="Task"/> completes. When run, the delegate will be
3488 /// passed the completed task as an argument.
3489 /// </param>
3490 /// <param name="continuationOptions">
3491 /// Options for when the continuation is scheduled and how it behaves. This includes criteria, such
3492 /// as <see
3493 /// cref="System.Threading.Tasks.TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as
3494 /// well as execution options, such as <see
3495 /// cref="System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>.
3496 /// </param>
3497 /// <returns>A new continuation <see cref="Task"/>.</returns>
3498 /// <remarks>
3499 /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has
3500 /// completed. If the continuation criteria specified through the <paramref
3501 /// name="continuationOptions"/> parameter are not met, the continuation task will be canceled
3502 /// instead of scheduled.
3503 /// </remarks>
3504 /// <exception cref="System.ArgumentNullException">
3505 /// The <paramref name="continuationAction"/> argument is null.
3506 /// </exception>
3507 /// <exception cref="System.ArgumentOutOfRangeException">
3508 /// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see
3509 /// cref="System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>.
3510 /// </exception>
3511 public Task ContinueWith(Action<Task> continuationAction, TaskContinuationOptions continuationOptions)
3513 return ContinueWith(continuationAction, TaskScheduler.Current, default, continuationOptions);
3516 /// <summary>
3517 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
3518 /// </summary>
3519 /// <param name="continuationAction">
3520 /// An action to run when the <see cref="Task"/> completes. When run, the delegate will be
3521 /// passed the completed task as an argument.
3522 /// </param>
3523 /// <param name="continuationOptions">
3524 /// Options for when the continuation is scheduled and how it behaves. This includes criteria, such
3525 /// as <see
3526 /// cref="System.Threading.Tasks.TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as
3527 /// well as execution options, such as <see
3528 /// cref="System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>.
3529 /// </param>
3530 /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new continuation task.</param>
3531 /// <param name="scheduler">
3532 /// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its
3533 /// execution.
3534 /// </param>
3535 /// <returns>A new continuation <see cref="Task"/>.</returns>
3536 /// <remarks>
3537 /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has
3538 /// completed. If the criteria specified through the <paramref name="continuationOptions"/> parameter
3539 /// are not met, the continuation task will be canceled instead of scheduled.
3540 /// </remarks>
3541 /// <exception cref="System.ArgumentNullException">
3542 /// The <paramref name="continuationAction"/> argument is null.
3543 /// </exception>
3544 /// <exception cref="System.ArgumentOutOfRangeException">
3545 /// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see
3546 /// cref="System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>.
3547 /// </exception>
3548 /// <exception cref="System.ArgumentNullException">
3549 /// The <paramref name="scheduler"/> argument is null.
3550 /// </exception>
3551 /// <exception cref="System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see>
3552 /// has already been disposed.
3553 /// </exception>
3554 public Task ContinueWith(Action<Task> continuationAction, CancellationToken cancellationToken,
3555 TaskContinuationOptions continuationOptions, TaskScheduler scheduler)
3557 return ContinueWith(continuationAction, scheduler, cancellationToken, continuationOptions);
3560 // Same as the above overload, just with a stack mark parameter.
3561 private Task ContinueWith(Action<Task> continuationAction, TaskScheduler scheduler,
3562 CancellationToken cancellationToken, TaskContinuationOptions continuationOptions)
3564 // Throw on continuation with null action
3565 if (continuationAction == null)
3567 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.continuationAction);
3570 // Throw on continuation with null TaskScheduler
3571 if (scheduler == null)
3573 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler);
3576 TaskCreationOptions creationOptions;
3577 InternalTaskOptions internalOptions;
3578 CreationOptionsFromContinuationOptions(continuationOptions, out creationOptions, out internalOptions);
3580 Task continuationTask = new ContinuationTaskFromTask(
3581 this, continuationAction, null,
3582 creationOptions, internalOptions
3585 // Register the continuation. If synchronous execution is requested, this may
3586 // actually invoke the continuation before returning.
3587 ContinueWithCore(continuationTask, scheduler, cancellationToken, continuationOptions);
3589 return continuationTask;
3591 #endregion
3593 #region Action<Task, Object> continuation
3595 /// <summary>
3596 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
3597 /// </summary>
3598 /// <param name="continuationAction">
3599 /// An action to run when the <see cref="Task"/> completes. When run, the delegate will be
3600 /// passed the completed task as and the caller-supplied state object as arguments.
3601 /// </param>
3602 /// <param name="state">An object representing data to be used by the continuation action.</param>
3603 /// <returns>A new continuation <see cref="Task"/>.</returns>
3604 /// <remarks>
3605 /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has
3606 /// completed, whether it completes due to running to completion successfully, faulting due to an
3607 /// unhandled exception, or exiting out early due to being canceled.
3608 /// </remarks>
3609 /// <exception cref="System.ArgumentNullException">
3610 /// The <paramref name="continuationAction"/> argument is null.
3611 /// </exception>
3612 public Task ContinueWith(Action<Task, object?> continuationAction, object? state)
3614 return ContinueWith(continuationAction, state, TaskScheduler.Current, default, TaskContinuationOptions.None);
3617 /// <summary>
3618 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
3619 /// </summary>
3620 /// <param name="continuationAction">
3621 /// An action to run when the <see cref="Task"/> completes. When run, the delegate will be
3622 /// passed the completed task and the caller-supplied state object as arguments.
3623 /// </param>
3624 /// <param name="state">An object representing data to be used by the continuation action.</param>
3625 /// <param name="cancellationToken"> The <see cref="CancellationToken"/> that will be assigned to the new continuation task.</param>
3626 /// <returns>A new continuation <see cref="Task"/>.</returns>
3627 /// <remarks>
3628 /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has
3629 /// completed, whether it completes due to running to completion successfully, faulting due to an
3630 /// unhandled exception, or exiting out early due to being canceled.
3631 /// </remarks>
3632 /// <exception cref="System.ArgumentNullException">
3633 /// The <paramref name="continuationAction"/> argument is null.
3634 /// </exception>
3635 /// <exception cref="System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see>
3636 /// has already been disposed.
3637 /// </exception>
3638 public Task ContinueWith(Action<Task, object?> continuationAction, object? state, CancellationToken cancellationToken)
3640 return ContinueWith(continuationAction, state, TaskScheduler.Current, cancellationToken, TaskContinuationOptions.None);
3643 /// <summary>
3644 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
3645 /// </summary>
3646 /// <param name="continuationAction">
3647 /// An action to run when the <see cref="Task"/> completes. When run, the delegate will be
3648 /// passed the completed task and the caller-supplied state object as arguments.
3649 /// </param>
3650 /// <param name="state">An object representing data to be used by the continuation action.</param>
3651 /// <param name="scheduler">
3652 /// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its execution.
3653 /// </param>
3654 /// <returns>A new continuation <see cref="Task"/>.</returns>
3655 /// <remarks>
3656 /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has
3657 /// completed, whether it completes due to running to completion successfully, faulting due to an
3658 /// unhandled exception, or exiting out early due to being canceled.
3659 /// </remarks>
3660 /// <exception cref="System.ArgumentNullException">
3661 /// The <paramref name="continuationAction"/> argument is null.
3662 /// </exception>
3663 /// <exception cref="System.ArgumentNullException">
3664 /// The <paramref name="scheduler"/> argument is null.
3665 /// </exception>
3666 public Task ContinueWith(Action<Task, object?> continuationAction, object? state, TaskScheduler scheduler)
3668 return ContinueWith(continuationAction, state, scheduler, default, TaskContinuationOptions.None);
3671 /// <summary>
3672 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
3673 /// </summary>
3674 /// <param name="continuationAction">
3675 /// An action to run when the <see cref="Task"/> completes. When run, the delegate will be
3676 /// passed the completed task and the caller-supplied state object as arguments.
3677 /// </param>
3678 /// <param name="state">An object representing data to be used by the continuation action.</param>
3679 /// <param name="continuationOptions">
3680 /// Options for when the continuation is scheduled and how it behaves. This includes criteria, such
3681 /// as <see
3682 /// cref="System.Threading.Tasks.TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as
3683 /// well as execution options, such as <see
3684 /// cref="System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>.
3685 /// </param>
3686 /// <returns>A new continuation <see cref="Task"/>.</returns>
3687 /// <remarks>
3688 /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has
3689 /// completed. If the continuation criteria specified through the <paramref
3690 /// name="continuationOptions"/> parameter are not met, the continuation task will be canceled
3691 /// instead of scheduled.
3692 /// </remarks>
3693 /// <exception cref="System.ArgumentNullException">
3694 /// The <paramref name="continuationAction"/> argument is null.
3695 /// </exception>
3696 /// <exception cref="System.ArgumentOutOfRangeException">
3697 /// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see
3698 /// cref="System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>.
3699 /// </exception>
3700 public Task ContinueWith(Action<Task, object?> continuationAction, object? state, TaskContinuationOptions continuationOptions)
3702 return ContinueWith(continuationAction, state, TaskScheduler.Current, default, continuationOptions);
3705 /// <summary>
3706 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
3707 /// </summary>
3708 /// <param name="continuationAction">
3709 /// An action to run when the <see cref="Task"/> completes. When run, the delegate will be
3710 /// passed the completed task and the caller-supplied state object as arguments.
3711 /// </param>
3712 /// <param name="state">An object representing data to be used by the continuation action.</param>
3713 /// <param name="continuationOptions">
3714 /// Options for when the continuation is scheduled and how it behaves. This includes criteria, such
3715 /// as <see
3716 /// cref="System.Threading.Tasks.TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as
3717 /// well as execution options, such as <see
3718 /// cref="System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>.
3719 /// </param>
3720 /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new continuation task.</param>
3721 /// <param name="scheduler">
3722 /// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its
3723 /// execution.
3724 /// </param>
3725 /// <returns>A new continuation <see cref="Task"/>.</returns>
3726 /// <remarks>
3727 /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has
3728 /// completed. If the criteria specified through the <paramref name="continuationOptions"/> parameter
3729 /// are not met, the continuation task will be canceled instead of scheduled.
3730 /// </remarks>
3731 /// <exception cref="System.ArgumentNullException">
3732 /// The <paramref name="continuationAction"/> argument is null.
3733 /// </exception>
3734 /// <exception cref="System.ArgumentOutOfRangeException">
3735 /// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see
3736 /// cref="System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>.
3737 /// </exception>
3738 /// <exception cref="System.ArgumentNullException">
3739 /// The <paramref name="scheduler"/> argument is null.
3740 /// </exception>
3741 /// <exception cref="System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see>
3742 /// has already been disposed.
3743 /// </exception>
3744 public Task ContinueWith(Action<Task, object?> continuationAction, object? state, CancellationToken cancellationToken,
3745 TaskContinuationOptions continuationOptions, TaskScheduler scheduler)
3747 return ContinueWith(continuationAction, state, scheduler, cancellationToken, continuationOptions);
3750 // Same as the above overload, just with a stack mark parameter.
3751 private Task ContinueWith(Action<Task, object?> continuationAction, object? state, TaskScheduler scheduler,
3752 CancellationToken cancellationToken, TaskContinuationOptions continuationOptions)
3754 // Throw on continuation with null action
3755 if (continuationAction == null)
3757 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.continuationAction);
3760 // Throw on continuation with null TaskScheduler
3761 if (scheduler == null)
3763 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler);
3766 TaskCreationOptions creationOptions;
3767 InternalTaskOptions internalOptions;
3768 CreationOptionsFromContinuationOptions(continuationOptions, out creationOptions, out internalOptions);
3770 Task continuationTask = new ContinuationTaskFromTask(
3771 this, continuationAction, state,
3772 creationOptions, internalOptions
3775 // Register the continuation. If synchronous execution is requested, this may
3776 // actually invoke the continuation before returning.
3777 ContinueWithCore(continuationTask, scheduler, cancellationToken, continuationOptions);
3779 return continuationTask;
3782 #endregion
3784 #region Func<Task, TResult> continuation
3786 /// <summary>
3787 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
3788 /// </summary>
3789 /// <typeparam name="TResult">
3790 /// The type of the result produced by the continuation.
3791 /// </typeparam>
3792 /// <param name="continuationFunction">
3793 /// A function to run when the <see cref="Task"/> completes. When run, the delegate will be
3794 /// passed the completed task as an argument.
3795 /// </param>
3796 /// <returns>A new continuation <see cref="Task{TResult}"/>.</returns>
3797 /// <remarks>
3798 /// The returned <see cref="Task{TResult}"/> will not be scheduled for execution until the current task has
3799 /// completed, whether it completes due to running to completion successfully, faulting due to an
3800 /// unhandled exception, or exiting out early due to being canceled.
3801 /// </remarks>
3802 /// <exception cref="System.ArgumentNullException">
3803 /// The <paramref name="continuationFunction"/> argument is null.
3804 /// </exception>
3805 public Task<TResult> ContinueWith<TResult>(Func<Task, TResult> continuationFunction)
3807 return ContinueWith<TResult>(continuationFunction, TaskScheduler.Current, default,
3808 TaskContinuationOptions.None);
3812 /// <summary>
3813 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
3814 /// </summary>
3815 /// <typeparam name="TResult">
3816 /// The type of the result produced by the continuation.
3817 /// </typeparam>
3818 /// <param name="continuationFunction">
3819 /// A function to run when the <see cref="Task"/> completes. When run, the delegate will be
3820 /// passed the completed task as an argument.
3821 /// </param>
3822 /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new continuation task.</param>
3823 /// <returns>A new continuation <see cref="Task{TResult}"/>.</returns>
3824 /// <remarks>
3825 /// The returned <see cref="Task{TResult}"/> will not be scheduled for execution until the current task has
3826 /// completed, whether it completes due to running to completion successfully, faulting due to an
3827 /// unhandled exception, or exiting out early due to being canceled.
3828 /// </remarks>
3829 /// <exception cref="System.ArgumentNullException">
3830 /// The <paramref name="continuationFunction"/> argument is null.
3831 /// </exception>
3832 /// <exception cref="System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see>
3833 /// has already been disposed.
3834 /// </exception>
3835 public Task<TResult> ContinueWith<TResult>(Func<Task, TResult> continuationFunction, CancellationToken cancellationToken)
3837 return ContinueWith<TResult>(continuationFunction, TaskScheduler.Current, cancellationToken, TaskContinuationOptions.None);
3840 /// <summary>
3841 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
3842 /// </summary>
3843 /// <typeparam name="TResult">
3844 /// The type of the result produced by the continuation.
3845 /// </typeparam>
3846 /// <param name="continuationFunction">
3847 /// A function to run when the <see cref="Task"/> completes. When run, the delegate will be
3848 /// passed the completed task as an argument.
3849 /// </param>
3850 /// <param name="scheduler">
3851 /// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its execution.
3852 /// </param>
3853 /// <returns>A new continuation <see cref="Task{TResult}"/>.</returns>
3854 /// <remarks>
3855 /// The returned <see cref="Task{TResult}"/> will not be scheduled for execution until the current task has
3856 /// completed, whether it completes due to running to completion successfully, faulting due to an
3857 /// unhandled exception, or exiting out early due to being canceled.
3858 /// </remarks>
3859 /// <exception cref="System.ArgumentNullException">
3860 /// The <paramref name="continuationFunction"/> argument is null.
3861 /// </exception>
3862 /// <exception cref="System.ArgumentNullException">
3863 /// The <paramref name="scheduler"/> argument is null.
3864 /// </exception>
3865 public Task<TResult> ContinueWith<TResult>(Func<Task, TResult> continuationFunction, TaskScheduler scheduler)
3867 return ContinueWith<TResult>(continuationFunction, scheduler, default, TaskContinuationOptions.None);
3870 /// <summary>
3871 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
3872 /// </summary>
3873 /// <typeparam name="TResult">
3874 /// The type of the result produced by the continuation.
3875 /// </typeparam>
3876 /// <param name="continuationFunction">
3877 /// A function to run when the <see cref="Task"/> completes. When run, the delegate will be
3878 /// passed the completed task as an argument.
3879 /// </param>
3880 /// <param name="continuationOptions">
3881 /// Options for when the continuation is scheduled and how it behaves. This includes criteria, such
3882 /// as <see
3883 /// cref="System.Threading.Tasks.TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as
3884 /// well as execution options, such as <see
3885 /// cref="System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>.
3886 /// </param>
3887 /// <returns>A new continuation <see cref="Task{TResult}"/>.</returns>
3888 /// <remarks>
3889 /// The returned <see cref="Task{TResult}"/> will not be scheduled for execution until the current task has
3890 /// completed. If the continuation criteria specified through the <paramref
3891 /// name="continuationOptions"/> parameter are not met, the continuation task will be canceled
3892 /// instead of scheduled.
3893 /// </remarks>
3894 /// <exception cref="System.ArgumentNullException">
3895 /// The <paramref name="continuationFunction"/> argument is null.
3896 /// </exception>
3897 /// <exception cref="System.ArgumentOutOfRangeException">
3898 /// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see
3899 /// cref="System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>.
3900 /// </exception>
3901 public Task<TResult> ContinueWith<TResult>(Func<Task, TResult> continuationFunction, TaskContinuationOptions continuationOptions)
3903 return ContinueWith<TResult>(continuationFunction, TaskScheduler.Current, default, continuationOptions);
3906 /// <summary>
3907 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
3908 /// </summary>
3909 /// <typeparam name="TResult">
3910 /// The type of the result produced by the continuation.
3911 /// </typeparam>
3912 /// <param name="continuationFunction">
3913 /// A function to run when the <see cref="Task"/> completes. When run, the delegate will be
3914 /// passed the completed task as an argument.
3915 /// </param>
3916 /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new continuation task.</param>
3917 /// <param name="continuationOptions">
3918 /// Options for when the continuation is scheduled and how it behaves. This includes criteria, such
3919 /// as <see
3920 /// cref="System.Threading.Tasks.TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as
3921 /// well as execution options, such as <see
3922 /// cref="System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>.
3923 /// </param>
3924 /// <param name="scheduler">
3925 /// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its
3926 /// execution.
3927 /// </param>
3928 /// <returns>A new continuation <see cref="Task{TResult}"/>.</returns>
3929 /// <remarks>
3930 /// The returned <see cref="Task{TResult}"/> will not be scheduled for execution until the current task has
3931 /// completed. If the criteria specified through the <paramref name="continuationOptions"/> parameter
3932 /// are not met, the continuation task will be canceled instead of scheduled.
3933 /// </remarks>
3934 /// <exception cref="System.ArgumentNullException">
3935 /// The <paramref name="continuationFunction"/> argument is null.
3936 /// </exception>
3937 /// <exception cref="System.ArgumentOutOfRangeException">
3938 /// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see
3939 /// cref="System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>.
3940 /// </exception>
3941 /// <exception cref="System.ArgumentNullException">
3942 /// The <paramref name="scheduler"/> argument is null.
3943 /// </exception>
3944 /// <exception cref="System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see>
3945 /// has already been disposed.
3946 /// </exception>
3947 public Task<TResult> ContinueWith<TResult>(Func<Task, TResult> continuationFunction, CancellationToken cancellationToken,
3948 TaskContinuationOptions continuationOptions, TaskScheduler scheduler)
3950 return ContinueWith<TResult>(continuationFunction, scheduler, cancellationToken, continuationOptions);
3953 // Same as the above overload, just with a stack mark parameter.
3954 private Task<TResult> ContinueWith<TResult>(Func<Task, TResult> continuationFunction, TaskScheduler scheduler,
3955 CancellationToken cancellationToken, TaskContinuationOptions continuationOptions)
3957 // Throw on continuation with null function
3958 if (continuationFunction == null)
3960 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.continuationFunction);
3963 // Throw on continuation with null task scheduler
3964 if (scheduler == null)
3966 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler);
3969 TaskCreationOptions creationOptions;
3970 InternalTaskOptions internalOptions;
3971 CreationOptionsFromContinuationOptions(continuationOptions, out creationOptions, out internalOptions);
3973 Task<TResult> continuationTask = new ContinuationResultTaskFromTask<TResult>(
3974 this, continuationFunction, null,
3975 creationOptions, internalOptions
3978 // Register the continuation. If synchronous execution is requested, this may
3979 // actually invoke the continuation before returning.
3980 ContinueWithCore(continuationTask, scheduler, cancellationToken, continuationOptions);
3982 return continuationTask;
3984 #endregion
3986 #region Func<Task, Object, TResult> continuation
3988 /// <summary>
3989 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
3990 /// </summary>
3991 /// <typeparam name="TResult">
3992 /// The type of the result produced by the continuation.
3993 /// </typeparam>
3994 /// <param name="continuationFunction">
3995 /// A function to run when the <see cref="Task"/> completes. When run, the delegate will be
3996 /// passed the completed task and the caller-supplied state object as arguments.
3997 /// </param>
3998 /// <param name="state">An object representing data to be used by the continuation function.</param>
3999 /// <returns>A new continuation <see cref="Task{TResult}"/>.</returns>
4000 /// <remarks>
4001 /// The returned <see cref="Task{TResult}"/> will not be scheduled for execution until the current task has
4002 /// completed, whether it completes due to running to completion successfully, faulting due to an
4003 /// unhandled exception, or exiting out early due to being canceled.
4004 /// </remarks>
4005 /// <exception cref="System.ArgumentNullException">
4006 /// The <paramref name="continuationFunction"/> argument is null.
4007 /// </exception>
4008 public Task<TResult> ContinueWith<TResult>(Func<Task, object?, TResult> continuationFunction, object? state)
4010 return ContinueWith<TResult>(continuationFunction, state, TaskScheduler.Current, default,
4011 TaskContinuationOptions.None);
4015 /// <summary>
4016 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
4017 /// </summary>
4018 /// <typeparam name="TResult">
4019 /// The type of the result produced by the continuation.
4020 /// </typeparam>
4021 /// <param name="continuationFunction">
4022 /// A function to run when the <see cref="Task"/> completes. When run, the delegate will be
4023 /// passed the completed task and the caller-supplied state object as arguments.
4024 /// </param>
4025 /// <param name="state">An object representing data to be used by the continuation function.</param>
4026 /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new continuation task.</param>
4027 /// <returns>A new continuation <see cref="Task{TResult}"/>.</returns>
4028 /// <remarks>
4029 /// The returned <see cref="Task{TResult}"/> will not be scheduled for execution until the current task has
4030 /// completed, whether it completes due to running to completion successfully, faulting due to an
4031 /// unhandled exception, or exiting out early due to being canceled.
4032 /// </remarks>
4033 /// <exception cref="System.ArgumentNullException">
4034 /// The <paramref name="continuationFunction"/> argument is null.
4035 /// </exception>
4036 /// <exception cref="System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see>
4037 /// has already been disposed.
4038 /// </exception>
4039 public Task<TResult> ContinueWith<TResult>(Func<Task, object?, TResult> continuationFunction, object? state, CancellationToken cancellationToken)
4041 return ContinueWith<TResult>(continuationFunction, state, TaskScheduler.Current, cancellationToken, TaskContinuationOptions.None);
4044 /// <summary>
4045 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
4046 /// </summary>
4047 /// <typeparam name="TResult">
4048 /// The type of the result produced by the continuation.
4049 /// </typeparam>
4050 /// <param name="continuationFunction">
4051 /// A function to run when the <see cref="Task"/> completes. When run, the delegate will be
4052 /// passed the completed task and the caller-supplied state object as arguments.
4053 /// </param>
4054 /// <param name="state">An object representing data to be used by the continuation function.</param>
4055 /// <param name="scheduler">
4056 /// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its execution.
4057 /// </param>
4058 /// <returns>A new continuation <see cref="Task{TResult}"/>.</returns>
4059 /// <remarks>
4060 /// The returned <see cref="Task{TResult}"/> will not be scheduled for execution until the current task has
4061 /// completed, whether it completes due to running to completion successfully, faulting due to an
4062 /// unhandled exception, or exiting out early due to being canceled.
4063 /// </remarks>
4064 /// <exception cref="System.ArgumentNullException">
4065 /// The <paramref name="continuationFunction"/> argument is null.
4066 /// </exception>
4067 /// <exception cref="System.ArgumentNullException">
4068 /// The <paramref name="scheduler"/> argument is null.
4069 /// </exception>
4070 public Task<TResult> ContinueWith<TResult>(Func<Task, object?, TResult> continuationFunction, object? state, TaskScheduler scheduler)
4072 return ContinueWith<TResult>(continuationFunction, state, scheduler, default, TaskContinuationOptions.None);
4075 /// <summary>
4076 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
4077 /// </summary>
4078 /// <typeparam name="TResult">
4079 /// The type of the result produced by the continuation.
4080 /// </typeparam>
4081 /// <param name="continuationFunction">
4082 /// A function to run when the <see cref="Task"/> completes. When run, the delegate will be
4083 /// passed the completed task and the caller-supplied state object as arguments.
4084 /// </param>
4085 /// <param name="state">An object representing data to be used by the continuation function.</param>
4086 /// <param name="continuationOptions">
4087 /// Options for when the continuation is scheduled and how it behaves. This includes criteria, such
4088 /// as <see
4089 /// cref="System.Threading.Tasks.TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as
4090 /// well as execution options, such as <see
4091 /// cref="System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>.
4092 /// </param>
4093 /// <returns>A new continuation <see cref="Task{TResult}"/>.</returns>
4094 /// <remarks>
4095 /// The returned <see cref="Task{TResult}"/> will not be scheduled for execution until the current task has
4096 /// completed. If the continuation criteria specified through the <paramref
4097 /// name="continuationOptions"/> parameter are not met, the continuation task will be canceled
4098 /// instead of scheduled.
4099 /// </remarks>
4100 /// <exception cref="System.ArgumentNullException">
4101 /// The <paramref name="continuationFunction"/> argument is null.
4102 /// </exception>
4103 /// <exception cref="System.ArgumentOutOfRangeException">
4104 /// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see
4105 /// cref="System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>.
4106 /// </exception>
4107 public Task<TResult> ContinueWith<TResult>(Func<Task, object?, TResult> continuationFunction, object? state, TaskContinuationOptions continuationOptions)
4109 return ContinueWith<TResult>(continuationFunction, state, TaskScheduler.Current, default, continuationOptions);
4112 /// <summary>
4113 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
4114 /// </summary>
4115 /// <typeparam name="TResult">
4116 /// The type of the result produced by the continuation.
4117 /// </typeparam>
4118 /// <param name="continuationFunction">
4119 /// A function to run when the <see cref="Task"/> completes. When run, the delegate will be
4120 /// passed the completed task and the caller-supplied state object as arguments.
4121 /// </param>
4122 /// <param name="state">An object representing data to be used by the continuation function.</param>
4123 /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new continuation task.</param>
4124 /// <param name="continuationOptions">
4125 /// Options for when the continuation is scheduled and how it behaves. This includes criteria, such
4126 /// as <see
4127 /// cref="System.Threading.Tasks.TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as
4128 /// well as execution options, such as <see
4129 /// cref="System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>.
4130 /// </param>
4131 /// <param name="scheduler">
4132 /// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its
4133 /// execution.
4134 /// </param>
4135 /// <returns>A new continuation <see cref="Task{TResult}"/>.</returns>
4136 /// <remarks>
4137 /// The returned <see cref="Task{TResult}"/> will not be scheduled for execution until the current task has
4138 /// completed. If the criteria specified through the <paramref name="continuationOptions"/> parameter
4139 /// are not met, the continuation task will be canceled instead of scheduled.
4140 /// </remarks>
4141 /// <exception cref="System.ArgumentNullException">
4142 /// The <paramref name="continuationFunction"/> argument is null.
4143 /// </exception>
4144 /// <exception cref="System.ArgumentOutOfRangeException">
4145 /// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see
4146 /// cref="System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>.
4147 /// </exception>
4148 /// <exception cref="System.ArgumentNullException">
4149 /// The <paramref name="scheduler"/> argument is null.
4150 /// </exception>
4151 /// <exception cref="System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see>
4152 /// has already been disposed.
4153 /// </exception>
4154 public Task<TResult> ContinueWith<TResult>(Func<Task, object?, TResult> continuationFunction, object? state, CancellationToken cancellationToken,
4155 TaskContinuationOptions continuationOptions, TaskScheduler scheduler)
4157 return ContinueWith<TResult>(continuationFunction, state, scheduler, cancellationToken, continuationOptions);
4160 // Same as the above overload, just with a stack mark parameter.
4161 private Task<TResult> ContinueWith<TResult>(Func<Task, object?, TResult> continuationFunction, object? state, TaskScheduler scheduler,
4162 CancellationToken cancellationToken, TaskContinuationOptions continuationOptions)
4164 // Throw on continuation with null function
4165 if (continuationFunction == null)
4167 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.continuationFunction);
4170 // Throw on continuation with null task scheduler
4171 if (scheduler == null)
4173 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler);
4176 TaskCreationOptions creationOptions;
4177 InternalTaskOptions internalOptions;
4178 CreationOptionsFromContinuationOptions(continuationOptions, out creationOptions, out internalOptions);
4180 Task<TResult> continuationTask = new ContinuationResultTaskFromTask<TResult>(
4181 this, continuationFunction, state,
4182 creationOptions, internalOptions
4185 // Register the continuation. If synchronous execution is requested, this may
4186 // actually invoke the continuation before returning.
4187 ContinueWithCore(continuationTask, scheduler, cancellationToken, continuationOptions);
4189 return continuationTask;
4191 #endregion
4193 /// <summary>
4194 /// Converts TaskContinuationOptions to TaskCreationOptions, and also does
4195 /// some validity checking along the way.
4196 /// </summary>
4197 /// <param name="continuationOptions">Incoming TaskContinuationOptions</param>
4198 /// <param name="creationOptions">Outgoing TaskCreationOptions</param>
4199 /// <param name="internalOptions">Outgoing InternalTaskOptions</param>
4200 internal static void CreationOptionsFromContinuationOptions(
4201 TaskContinuationOptions continuationOptions,
4202 out TaskCreationOptions creationOptions,
4203 out InternalTaskOptions internalOptions)
4205 // This is used a couple of times below
4206 const TaskContinuationOptions NotOnAnything =
4207 TaskContinuationOptions.NotOnCanceled |
4208 TaskContinuationOptions.NotOnFaulted |
4209 TaskContinuationOptions.NotOnRanToCompletion;
4211 const TaskContinuationOptions CreationOptionsMask =
4212 TaskContinuationOptions.PreferFairness |
4213 TaskContinuationOptions.LongRunning |
4214 TaskContinuationOptions.DenyChildAttach |
4215 TaskContinuationOptions.HideScheduler |
4216 TaskContinuationOptions.AttachedToParent |
4217 TaskContinuationOptions.RunContinuationsAsynchronously;
4219 // Check that LongRunning and ExecuteSynchronously are not specified together
4220 const TaskContinuationOptions IllegalMask = TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.LongRunning;
4221 if ((continuationOptions & IllegalMask) == IllegalMask)
4223 ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.continuationOptions, ExceptionResource.Task_ContinueWith_ESandLR);
4226 // Check that no illegal options were specified
4227 if ((continuationOptions &
4228 ~(CreationOptionsMask | NotOnAnything |
4229 TaskContinuationOptions.LazyCancellation | TaskContinuationOptions.ExecuteSynchronously)) != 0)
4231 ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.continuationOptions);
4234 // Check that we didn't specify "not on anything"
4235 if ((continuationOptions & NotOnAnything) == NotOnAnything)
4237 ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.continuationOptions, ExceptionResource.Task_ContinueWith_NotOnAnything);
4240 // This passes over all but LazyCancellation, which has no representation in TaskCreationOptions
4241 creationOptions = (TaskCreationOptions)(continuationOptions & CreationOptionsMask);
4243 // internalOptions has at least ContinuationTask and possibly LazyCancellation
4244 internalOptions = (continuationOptions & TaskContinuationOptions.LazyCancellation) != 0 ?
4245 InternalTaskOptions.ContinuationTask | InternalTaskOptions.LazyCancellation :
4246 InternalTaskOptions.ContinuationTask;
4250 /// <summary>
4251 /// Registers the continuation and possibly runs it (if the task is already finished).
4252 /// </summary>
4253 /// <param name="continuationTask">The continuation task itself.</param>
4254 /// <param name="scheduler">TaskScheduler with which to associate continuation task.</param>
4255 /// <param name="options">Restrictions on when the continuation becomes active.</param>
4256 internal void ContinueWithCore(Task continuationTask,
4257 TaskScheduler scheduler,
4258 CancellationToken cancellationToken,
4259 TaskContinuationOptions options)
4261 Debug.Assert(continuationTask != null, "Task.ContinueWithCore(): null continuationTask");
4262 Debug.Assert(scheduler != null, "Task.ContinueWithCore(): null scheduler");
4263 Debug.Assert(!continuationTask.IsCompleted, "Did not expect continuationTask to be completed");
4265 // Create a TaskContinuation
4266 TaskContinuation continuation = new StandardTaskContinuation(continuationTask, options, scheduler);
4268 // If cancellationToken is cancellable, then assign it.
4269 if (cancellationToken.CanBeCanceled)
4271 if (IsCompleted || cancellationToken.IsCancellationRequested)
4273 // If the antecedent has completed, then we will not be queuing up
4274 // the continuation in the antecedent's continuation list. Likewise,
4275 // if the cancellationToken has been canceled, continuationTask will
4276 // be completed in the AssignCancellationToken call below, and there
4277 // is no need to queue the continuation to the antecedent's continuation
4278 // list. In either of these two cases, we will pass "null" for the antecedent,
4279 // meaning "the cancellation callback should not attempt to remove the
4280 // continuation from its antecedent's continuation list".
4281 continuationTask.AssignCancellationToken(cancellationToken, null, null);
4283 else
4285 // The antecedent is not yet complete, so there is a pretty good chance
4286 // that the continuation will be queued up in the antecedent. Assign the
4287 // cancellation token with information about the antecedent, so that the
4288 // continuation can be dequeued upon the signalling of the token.
4290 // It's possible that the antecedent completes before the call to AddTaskContinuation,
4291 // and that is a benign race condition. It just means that the cancellation will result in
4292 // a futile search of the antecedent's continuation list.
4293 continuationTask.AssignCancellationToken(cancellationToken, this, continuation);
4297 // In the case of a pre-canceled token, continuationTask will have been completed
4298 // in a Canceled state by now. If such is the case, there is no need to go through
4299 // the motions of queuing up the continuation for eventual execution.
4300 if (!continuationTask.IsCompleted)
4302 // We need additional correlation produced here to ensure that at least the continuation
4303 // code will be correlatable to the currrent activity that initiated "this" task:
4304 // . when the antecendent ("this") is a promise we have very little control over where
4305 // the code for the promise will run (e.g. it can be a task from a user provided
4306 // TaskCompletionSource or from a classic Begin/End async operation); this user or
4307 // system code will likely not have stamped an activity id on the thread, so there's
4308 // generally no easy correlation that can be provided between the current activity
4309 // and the promise. Also the continuation code may run practically on any thread.
4310 // Since there may be no correlation between the current activity and the TCS's task
4311 // activity, we ensure we at least create a correlation from the current activity to
4312 // the continuation that runs when the promise completes.
4313 if ((this.Options & (TaskCreationOptions)InternalTaskOptions.PromiseTask) != 0 &&
4314 !(this is ITaskCompletionAction))
4316 TplEventSource log = TplEventSource.Log;
4317 if (log.IsEnabled())
4319 log.AwaitTaskContinuationScheduled(TaskScheduler.Current.Id, Task.CurrentId ?? 0, continuationTask.Id);
4323 // Attempt to enqueue the continuation
4324 bool continuationQueued = AddTaskContinuation(continuation, addBeforeOthers: false);
4326 // If the continuation was not queued (because the task completed), then run it now.
4327 if (!continuationQueued) continuation.Run(this, canInlineContinuationTask: true);
4330 #endregion
4332 // Adds a lightweight completion action to a task. This is similar to a continuation
4333 // task except that it is stored as an action, and thus does not require the allocation/
4334 // execution resources of a continuation task.
4336 // Used internally by ContinueWhenAll() and ContinueWhenAny().
4337 internal void AddCompletionAction(ITaskCompletionAction action)
4339 AddCompletionAction(action, addBeforeOthers: false);
4342 internal void AddCompletionAction(ITaskCompletionAction action, bool addBeforeOthers)
4344 if (!AddTaskContinuation(action, addBeforeOthers))
4345 action.Invoke(this); // run the action directly if we failed to queue the continuation (i.e., the task completed)
4348 // Support method for AddTaskContinuation that takes care of multi-continuation logic.
4349 // Returns true if and only if the continuation was successfully queued.
4350 // THIS METHOD ASSUMES THAT m_continuationObject IS NOT NULL. That case was taken
4351 // care of in the calling method, AddTaskContinuation().
4352 private bool AddTaskContinuationComplex(object tc, bool addBeforeOthers)
4354 Debug.Assert(tc != null, "Expected non-null tc object in AddTaskContinuationComplex");
4356 object? oldValue = m_continuationObject;
4358 // Logic for the case where we were previously storing a single continuation
4359 if ((oldValue != s_taskCompletionSentinel) && (!(oldValue is List<object?>)))
4361 // Construct a new TaskContinuation list and CAS it in.
4362 Interlocked.CompareExchange(ref m_continuationObject, new List<object?> { oldValue }, oldValue);
4364 // We might be racing against another thread converting the single into
4365 // a list, or we might be racing against task completion, so resample "list"
4366 // below.
4369 // m_continuationObject is guaranteed at this point to be either a List or
4370 // s_taskCompletionSentinel.
4371 List<object?>? list = m_continuationObject as List<object?>;
4372 Debug.Assert((list != null) || (m_continuationObject == s_taskCompletionSentinel),
4373 "Expected m_continuationObject to be list or sentinel");
4375 // If list is null, it can only mean that s_taskCompletionSentinel has been exchanged
4376 // into m_continuationObject. Thus, the task has completed and we should return false
4377 // from this method, as we will not be queuing up the continuation.
4378 if (list != null)
4380 lock (list)
4382 // It is possible for the task to complete right after we snap the copy of
4383 // the list. If so, then fall through and return false without queuing the
4384 // continuation.
4385 if (m_continuationObject != s_taskCompletionSentinel)
4387 // Before growing the list we remove possible null entries that are the
4388 // result from RemoveContinuations()
4389 if (list.Count == list.Capacity)
4391 list.RemoveAll(l => l == null);
4394 if (addBeforeOthers)
4395 list.Insert(0, tc);
4396 else
4397 list.Add(tc);
4399 return true; // continuation successfully queued, so return true.
4404 // We didn't succeed in queuing the continuation, so return false.
4405 return false;
4408 // Record a continuation task or action.
4409 // Return true if and only if we successfully queued a continuation.
4410 private bool AddTaskContinuation(object tc, bool addBeforeOthers)
4412 Debug.Assert(tc != null);
4414 // Make sure that, if someone calls ContinueWith() right after waiting for the predecessor to complete,
4415 // we don't queue up a continuation.
4416 if (IsCompleted) return false;
4418 // Try to just jam tc into m_continuationObject
4419 if ((m_continuationObject != null) || (Interlocked.CompareExchange(ref m_continuationObject, tc, null) != null))
4421 // If we get here, it means that we failed to CAS tc into m_continuationObject.
4422 // Therefore, we must go the more complicated route.
4423 return AddTaskContinuationComplex(tc, addBeforeOthers);
4425 else return true;
4428 // Removes a continuation task from m_continuations
4429 internal void RemoveContinuation(object continuationObject) // could be TaskContinuation or Action<Task>
4431 // We need to snap a local reference to m_continuations since reading a volatile object is more costly.
4432 // Also to prevent the value to be changed as result of a race condition with another method.
4433 object? continuationsLocalRef = m_continuationObject;
4435 // Task is completed. Nothing to do here.
4436 if (continuationsLocalRef == s_taskCompletionSentinel) return;
4438 List<object?>? continuationsLocalListRef = continuationsLocalRef as List<object?>;
4439 if (continuationsLocalListRef is null)
4441 // This is not a list. If we have a single object (the one we want to remove) we try to replace it with an empty list.
4442 // Note we cannot go back to a null state, since it will mess up the AddTaskContinuation logic.
4443 if (Interlocked.CompareExchange(ref m_continuationObject, new List<object?>(), continuationObject) != continuationObject)
4445 // If we fail it means that either AddContinuationComplex won the race condition and m_continuationObject is now a List
4446 // that contains the element we want to remove. Or FinishContinuations set the s_taskCompletionSentinel.
4447 // So we should try to get a list one more time
4448 continuationsLocalListRef = m_continuationObject as List<object?>;
4450 else
4452 // Exchange was successful so we can skip the last comparison
4453 return;
4457 // if continuationsLocalRef == null it means s_taskCompletionSentinel has been set already and there is nothing else to do.
4458 if (continuationsLocalListRef != null)
4460 lock (continuationsLocalListRef)
4462 // There is a small chance that this task completed since we took a local snapshot into
4463 // continuationsLocalRef. In that case, just return; we don't want to be manipulating the
4464 // continuation list as it is being processed.
4465 if (m_continuationObject == s_taskCompletionSentinel) return;
4467 // Find continuationObject in the continuation list
4468 int index = continuationsLocalListRef.IndexOf(continuationObject);
4470 if (index != -1)
4472 // null out that TaskContinuation entry, which will be interpreted as "to be cleaned up"
4473 continuationsLocalListRef[index] = null;
4480 // Wait methods
4483 /// <summary>
4484 /// Waits for all of the provided <see cref="Task"/> objects to complete execution.
4485 /// </summary>
4486 /// <param name="tasks">
4487 /// An array of <see cref="Task"/> instances on which to wait.
4488 /// </param>
4489 /// <exception cref="System.ArgumentNullException">
4490 /// The <paramref name="tasks"/> argument is null.
4491 /// </exception>
4492 /// <exception cref="System.ArgumentNullException">
4493 /// The <paramref name="tasks"/> argument contains a null element.
4494 /// </exception>
4495 /// <exception cref="System.AggregateException">
4496 /// At least one of the <see cref="Task"/> instances was canceled -or- an exception was thrown during
4497 /// the execution of at least one of the <see cref="Task"/> instances.
4498 /// </exception>
4499 [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
4500 public static void WaitAll(params Task[] tasks)
4502 #if DEBUG
4503 bool waitResult =
4504 #endif
4505 WaitAllCore(tasks, Timeout.Infinite, default);
4507 #if DEBUG
4508 Debug.Assert(waitResult, "expected wait to succeed");
4509 #endif
4512 /// <summary>
4513 /// Waits for all of the provided <see cref="Task"/> objects to complete execution.
4514 /// </summary>
4515 /// <returns>
4516 /// true if all of the <see cref="Task"/> instances completed execution within the allotted time;
4517 /// otherwise, false.
4518 /// </returns>
4519 /// <param name="tasks">
4520 /// An array of <see cref="Task"/> instances on which to wait.
4521 /// </param>
4522 /// <param name="timeout">
4523 /// A <see cref="System.TimeSpan"/> that represents the number of milliseconds to wait, or a <see
4524 /// cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely.
4525 /// </param>
4526 /// <exception cref="System.ArgumentNullException">
4527 /// The <paramref name="tasks"/> argument is null.
4528 /// </exception>
4529 /// <exception cref="System.ArgumentException">
4530 /// The <paramref name="tasks"/> argument contains a null element.
4531 /// </exception>
4532 /// <exception cref="System.AggregateException">
4533 /// At least one of the <see cref="Task"/> instances was canceled -or- an exception was thrown during
4534 /// the execution of at least one of the <see cref="Task"/> instances.
4535 /// </exception>
4536 /// <exception cref="System.ArgumentOutOfRangeException">
4537 /// <paramref name="timeout"/> is a negative number other than -1 milliseconds, which represents an
4538 /// infinite time-out -or- timeout is greater than
4539 /// <see cref="int.MaxValue"/>.
4540 /// </exception>
4541 [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
4542 public static bool WaitAll(Task[] tasks, TimeSpan timeout)
4544 long totalMilliseconds = (long)timeout.TotalMilliseconds;
4545 if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue)
4547 ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.timeout);
4550 return WaitAllCore(tasks, (int)totalMilliseconds, default);
4553 /// <summary>
4554 /// Waits for all of the provided <see cref="Task"/> objects to complete execution.
4555 /// </summary>
4556 /// <returns>
4557 /// true if all of the <see cref="Task"/> instances completed execution within the allotted time;
4558 /// otherwise, false.
4559 /// </returns>
4560 /// <param name="millisecondsTimeout">
4561 /// The number of milliseconds to wait, or <see cref="System.Threading.Timeout.Infinite"/> (-1) to
4562 /// wait indefinitely.</param>
4563 /// <param name="tasks">An array of <see cref="Task"/> instances on which to wait.
4564 /// </param>
4565 /// <exception cref="System.ArgumentNullException">
4566 /// The <paramref name="tasks"/> argument is null.
4567 /// </exception>
4568 /// <exception cref="System.ArgumentException">
4569 /// The <paramref name="tasks"/> argument contains a null element.
4570 /// </exception>
4571 /// <exception cref="System.AggregateException">
4572 /// At least one of the <see cref="Task"/> instances was canceled -or- an exception was thrown during
4573 /// the execution of at least one of the <see cref="Task"/> instances.
4574 /// </exception>
4575 /// <exception cref="System.ArgumentOutOfRangeException">
4576 /// <paramref name="millisecondsTimeout"/> is a negative number other than -1, which represents an
4577 /// infinite time-out.
4578 /// </exception>
4579 [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
4580 public static bool WaitAll(Task[] tasks, int millisecondsTimeout)
4582 return WaitAllCore(tasks, millisecondsTimeout, default);
4585 /// <summary>
4586 /// Waits for all of the provided <see cref="Task"/> objects to complete execution.
4587 /// </summary>
4588 /// <param name="tasks">
4589 /// An array of <see cref="Task"/> instances on which to wait.
4590 /// </param>
4591 /// <param name="cancellationToken">
4592 /// A <see cref="CancellationToken"/> to observe while waiting for the tasks to complete.
4593 /// </param>
4594 /// <exception cref="System.ArgumentNullException">
4595 /// The <paramref name="tasks"/> argument is null.
4596 /// </exception>
4597 /// <exception cref="System.ArgumentException">
4598 /// The <paramref name="tasks"/> argument contains a null element.
4599 /// </exception>
4600 /// <exception cref="System.AggregateException">
4601 /// At least one of the <see cref="Task"/> instances was canceled -or- an exception was thrown during
4602 /// the execution of at least one of the <see cref="Task"/> instances.
4603 /// </exception>
4604 /// <exception cref="System.OperationCanceledException">
4605 /// The <paramref name="cancellationToken"/> was canceled.
4606 /// </exception>
4607 [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
4608 public static void WaitAll(Task[] tasks, CancellationToken cancellationToken)
4610 WaitAllCore(tasks, Timeout.Infinite, cancellationToken);
4613 /// <summary>
4614 /// Waits for all of the provided <see cref="Task"/> objects to complete execution.
4615 /// </summary>
4616 /// <returns>
4617 /// true if all of the <see cref="Task"/> instances completed execution within the allotted time;
4618 /// otherwise, false.
4619 /// </returns>
4620 /// <param name="tasks">
4621 /// An array of <see cref="Task"/> instances on which to wait.
4622 /// </param>
4623 /// <param name="millisecondsTimeout">
4624 /// The number of milliseconds to wait, or <see cref="System.Threading.Timeout.Infinite"/> (-1) to
4625 /// wait indefinitely.
4626 /// </param>
4627 /// <param name="cancellationToken">
4628 /// A <see cref="CancellationToken"/> to observe while waiting for the tasks to complete.
4629 /// </param>
4630 /// <exception cref="System.ArgumentNullException">
4631 /// The <paramref name="tasks"/> argument is null.
4632 /// </exception>
4633 /// <exception cref="System.ArgumentException">
4634 /// The <paramref name="tasks"/> argument contains a null element.
4635 /// </exception>
4636 /// <exception cref="System.AggregateException">
4637 /// At least one of the <see cref="Task"/> instances was canceled -or- an exception was thrown during
4638 /// the execution of at least one of the <see cref="Task"/> instances.
4639 /// </exception>
4640 /// <exception cref="System.ArgumentOutOfRangeException">
4641 /// <paramref name="millisecondsTimeout"/> is a negative number other than -1, which represents an
4642 /// infinite time-out.
4643 /// </exception>
4644 /// <exception cref="System.OperationCanceledException">
4645 /// The <paramref name="cancellationToken"/> was canceled.
4646 /// </exception>
4647 [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
4648 public static bool WaitAll(Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken) =>
4649 WaitAllCore(tasks, millisecondsTimeout, cancellationToken);
4651 // Separated out to allow it to be optimized (caller is marked NoOptimization for VS parallel debugger
4652 // to be able to see the method on the stack and inspect arguments).
4653 private static bool WaitAllCore(Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken)
4655 if (tasks == null)
4657 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks);
4659 if (millisecondsTimeout < -1)
4661 ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.millisecondsTimeout);
4664 cancellationToken.ThrowIfCancellationRequested(); // early check before we make any allocations
4667 // In this WaitAll() implementation we have 2 alternate code paths for a task to be handled:
4668 // CODEPATH1: skip an already completed task, CODEPATH2: actually wait on tasks
4669 // We make sure that the exception behavior of Task.Wait() is replicated the same for tasks handled in either of these codepaths
4672 List<Exception>? exceptions = null;
4673 List<Task>? waitedOnTaskList = null;
4674 List<Task>? notificationTasks = null;
4676 // If any of the waited-upon tasks end as Faulted or Canceled, set these to true.
4677 bool exceptionSeen = false, cancellationSeen = false;
4679 bool returnValue = true;
4681 // Collects incomplete tasks in "waitedOnTaskList"
4682 for (int i = tasks.Length - 1; i >= 0; i--)
4684 Task task = tasks[i];
4686 if (task == null)
4688 ThrowHelper.ThrowArgumentException(ExceptionResource.Task_WaitMulti_NullTask, ExceptionArgument.tasks);
4691 bool taskIsCompleted = task.IsCompleted;
4692 if (!taskIsCompleted)
4694 // try inlining the task only if we have an infinite timeout and an empty cancellation token
4695 if (millisecondsTimeout != Timeout.Infinite || cancellationToken.CanBeCanceled)
4697 // We either didn't attempt inline execution because we had a non-infinite timeout or we had a cancellable token.
4698 // In all cases we need to do a full wait on the task (=> add its event into the list.)
4699 AddToList(task, ref waitedOnTaskList, initSize: tasks.Length);
4701 else
4703 // We are eligible for inlining. If it doesn't work, we'll do a full wait.
4704 taskIsCompleted = task.WrappedTryRunInline() && task.IsCompleted; // A successful TryRunInline doesn't guarantee completion
4705 if (!taskIsCompleted) AddToList(task, ref waitedOnTaskList, initSize: tasks.Length);
4709 if (taskIsCompleted)
4711 if (task.IsFaulted) exceptionSeen = true;
4712 else if (task.IsCanceled) cancellationSeen = true;
4713 if (task.IsWaitNotificationEnabled) AddToList(task, ref notificationTasks, initSize: 1);
4717 if (waitedOnTaskList != null)
4719 // Block waiting for the tasks to complete.
4720 returnValue = WaitAllBlockingCore(waitedOnTaskList, millisecondsTimeout, cancellationToken);
4722 // If the wait didn't time out, ensure exceptions are propagated, and if a debugger is
4723 // attached and one of these tasks requires it, that we notify the debugger of a wait completion.
4724 if (returnValue)
4726 // Add any exceptions for this task to the collection, and if it's wait
4727 // notification bit is set, store it to operate on at the end.
4728 foreach (Task task in waitedOnTaskList)
4730 if (task.IsFaulted) exceptionSeen = true;
4731 else if (task.IsCanceled) cancellationSeen = true;
4732 if (task.IsWaitNotificationEnabled) AddToList(task, ref notificationTasks, initSize: 1);
4736 // We need to prevent the tasks array from being GC'ed until we come out of the wait.
4737 // This is necessary so that the Parallel Debugger can traverse it during the long wait and
4738 // deduce waiter/waitee relationships
4739 GC.KeepAlive(tasks);
4742 // Now that we're done and about to exit, if the wait completed and if we have
4743 // any tasks with a notification bit set, signal the debugger if any requires it.
4744 if (returnValue && notificationTasks != null)
4746 // Loop through each task tha that had its bit set, and notify the debugger
4747 // about the first one that requires it. The debugger will reset the bit
4748 // for any tasks we don't notify of as soon as we break, so we only need to notify
4749 // for one.
4750 foreach (Task task in notificationTasks)
4752 if (task.NotifyDebuggerOfWaitCompletionIfNecessary()) break;
4756 // If one or more threw exceptions, aggregate and throw them.
4757 if (returnValue && (exceptionSeen || cancellationSeen))
4759 // If the WaitAll was canceled and tasks were canceled but not faulted,
4760 // prioritize throwing an OCE for canceling the WaitAll over throwing an
4761 // AggregateException for all of the canceled Tasks. This helps
4762 // to bring determinism to an otherwise non-determistic case of using
4763 // the same token to cancel both the WaitAll and the Tasks.
4764 if (!exceptionSeen) cancellationToken.ThrowIfCancellationRequested();
4766 // Now gather up and throw all of the exceptions.
4767 foreach (Task task in tasks) AddExceptionsForCompletedTask(ref exceptions, task);
4768 Debug.Assert(exceptions != null, "Should have seen at least one exception");
4769 ThrowHelper.ThrowAggregateException(exceptions);
4772 return returnValue;
4775 /// <summary>Adds an element to the list, initializing the list if it's null.</summary>
4776 /// <typeparam name="T">Specifies the type of data stored in the list.</typeparam>
4777 /// <param name="item">The item to add.</param>
4778 /// <param name="list">The list.</param>
4779 /// <param name="initSize">The size to which to initialize the list if the list is null.</param>
4780 private static void AddToList<T>(T item, ref List<T>? list, int initSize)
4782 if (list == null) list = new List<T>(initSize);
4783 list.Add(item);
4786 /// <summary>Performs a blocking WaitAll on the vetted list of tasks.</summary>
4787 /// <param name="tasks">The tasks, which have already been checked and filtered for completion.</param>
4788 /// <param name="millisecondsTimeout">The timeout.</param>
4789 /// <param name="cancellationToken">The cancellation token.</param>
4790 /// <returns>true if all of the tasks completed; otherwise, false.</returns>
4791 private static bool WaitAllBlockingCore(List<Task> tasks, int millisecondsTimeout, CancellationToken cancellationToken)
4793 Debug.Assert(tasks != null, "Expected a non-null list of tasks");
4794 Debug.Assert(tasks.Count > 0, "Expected at least one task");
4796 bool waitCompleted = false;
4797 var mres = new SetOnCountdownMres(tasks.Count);
4800 foreach (Task task in tasks)
4802 task.AddCompletionAction(mres, addBeforeOthers: true);
4804 waitCompleted = mres.Wait(millisecondsTimeout, cancellationToken);
4806 finally
4808 if (!waitCompleted)
4810 foreach (Task task in tasks)
4812 if (!task.IsCompleted) task.RemoveContinuation(mres);
4815 // It's ok that we don't dispose of the MRES here, as we never
4816 // access the MRES' WaitHandle, and thus no finalizable resources
4817 // are actually created. We don't always just Dispose it because
4818 // a continuation that's accessing the MRES could still be executing.
4820 return waitCompleted;
4823 // A ManualResetEventSlim that will get Set after Invoke is called count times.
4824 // This allows us to replace this logic:
4825 // var mres = new ManualResetEventSlim(tasks.Count);
4826 // Action<Task> completionAction = delegate { if (Interlocked.Decrement(ref count) == 0) mres.Set(); };
4827 // foreach (var task in tasks) task.AddCompletionAction(completionAction);
4828 // with this logic:
4829 // var mres = new SetOnCountdownMres(tasks.Count);
4830 // foreach (var task in tasks) task.AddCompletionAction(mres);
4831 // which saves a couple of allocations.
4833 // Used in WaitAllBlockingCore (above).
4834 private sealed class SetOnCountdownMres : ManualResetEventSlim, ITaskCompletionAction
4836 private int _count;
4838 internal SetOnCountdownMres(int count)
4840 Debug.Assert(count > 0, "Expected count > 0");
4841 _count = count;
4844 public void Invoke(Task completingTask)
4846 if (Interlocked.Decrement(ref _count) == 0) Set();
4847 Debug.Assert(_count >= 0, "Count should never go below 0");
4850 public bool InvokeMayRunArbitraryCode => false;
4853 /// <summary>
4854 /// This internal function is only meant to be called by WaitAll()
4855 /// If the completed task is canceled or it has other exceptions, here we will add those
4856 /// into the passed in exception list (which will be lazily initialized here).
4857 /// </summary>
4858 internal static void AddExceptionsForCompletedTask(ref List<Exception>? exceptions, Task t)
4860 AggregateException? ex = t.GetExceptions(true);
4861 if (ex != null)
4863 // make sure the task's exception observed status is set appropriately
4864 // it's possible that WaitAll was called by the parent of an attached child,
4865 // this will make sure it won't throw again in the implicit wait
4866 t.UpdateExceptionObservedStatus();
4868 if (exceptions == null)
4870 exceptions = new List<Exception>(ex.InnerExceptions.Count);
4873 exceptions.AddRange(ex.InnerExceptions);
4878 /// <summary>
4879 /// Waits for any of the provided <see cref="Task"/> objects to complete execution.
4880 /// </summary>
4881 /// <param name="tasks">
4882 /// An array of <see cref="Task"/> instances on which to wait.
4883 /// </param>
4884 /// <returns>The index of the completed task in the <paramref name="tasks"/> array argument.</returns>
4885 /// <exception cref="System.ArgumentNullException">
4886 /// The <paramref name="tasks"/> argument is null.
4887 /// </exception>
4888 /// <exception cref="System.ArgumentException">
4889 /// The <paramref name="tasks"/> argument contains a null element.
4890 /// </exception>
4891 [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
4892 public static int WaitAny(params Task[] tasks)
4894 int waitResult = WaitAnyCore(tasks, Timeout.Infinite, default);
4895 Debug.Assert(tasks.Length == 0 || waitResult != -1, "expected wait to succeed");
4896 return waitResult;
4899 /// <summary>
4900 /// Waits for any of the provided <see cref="Task"/> objects to complete execution.
4901 /// </summary>
4902 /// <param name="tasks">
4903 /// An array of <see cref="Task"/> instances on which to wait.
4904 /// </param>
4905 /// <param name="timeout">
4906 /// A <see cref="System.TimeSpan"/> that represents the number of milliseconds to wait, or a <see
4907 /// cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely.
4908 /// </param>
4909 /// <returns>
4910 /// The index of the completed task in the <paramref name="tasks"/> array argument, or -1 if the
4911 /// timeout occurred.
4912 /// </returns>
4913 /// <exception cref="System.ArgumentNullException">
4914 /// The <paramref name="tasks"/> argument is null.
4915 /// </exception>
4916 /// <exception cref="System.ArgumentException">
4917 /// The <paramref name="tasks"/> argument contains a null element.
4918 /// </exception>
4919 /// <exception cref="System.ArgumentOutOfRangeException">
4920 /// <paramref name="timeout"/> is a negative number other than -1 milliseconds, which represents an
4921 /// infinite time-out -or- timeout is greater than
4922 /// <see cref="int.MaxValue"/>.
4923 /// </exception>
4924 [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
4925 public static int WaitAny(Task[] tasks, TimeSpan timeout)
4927 long totalMilliseconds = (long)timeout.TotalMilliseconds;
4928 if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue)
4930 ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.timeout);
4933 return WaitAnyCore(tasks, (int)totalMilliseconds, default);
4936 /// <summary>
4937 /// Waits for any of the provided <see cref="Task"/> objects to complete execution.
4938 /// </summary>
4939 /// <param name="tasks">
4940 /// An array of <see cref="Task"/> instances on which to wait.
4941 /// </param>
4942 /// <param name="cancellationToken">
4943 /// A <see cref="CancellationToken"/> to observe while waiting for a task to complete.
4944 /// </param>
4945 /// <returns>
4946 /// The index of the completed task in the <paramref name="tasks"/> array argument.
4947 /// </returns>
4948 /// <exception cref="System.ArgumentNullException">
4949 /// The <paramref name="tasks"/> argument is null.
4950 /// </exception>
4951 /// <exception cref="System.ArgumentException">
4952 /// The <paramref name="tasks"/> argument contains a null element.
4953 /// </exception>
4954 /// <exception cref="System.OperationCanceledException">
4955 /// The <paramref name="cancellationToken"/> was canceled.
4956 /// </exception>
4957 [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
4958 public static int WaitAny(Task[] tasks, CancellationToken cancellationToken)
4960 return WaitAnyCore(tasks, Timeout.Infinite, cancellationToken);
4963 /// <summary>
4964 /// Waits for any of the provided <see cref="Task"/> objects to complete execution.
4965 /// </summary>
4966 /// <param name="tasks">
4967 /// An array of <see cref="Task"/> instances on which to wait.
4968 /// </param>
4969 /// <param name="millisecondsTimeout">
4970 /// The number of milliseconds to wait, or <see cref="System.Threading.Timeout.Infinite"/> (-1) to
4971 /// wait indefinitely.
4972 /// </param>
4973 /// <returns>
4974 /// The index of the completed task in the <paramref name="tasks"/> array argument, or -1 if the
4975 /// timeout occurred.
4976 /// </returns>
4977 /// <exception cref="System.ArgumentNullException">
4978 /// The <paramref name="tasks"/> argument is null.
4979 /// </exception>
4980 /// <exception cref="System.ArgumentException">
4981 /// The <paramref name="tasks"/> argument contains a null element.
4982 /// </exception>
4983 /// <exception cref="System.ArgumentOutOfRangeException">
4984 /// <paramref name="millisecondsTimeout"/> is a negative number other than -1, which represents an
4985 /// infinite time-out.
4986 /// </exception>
4987 [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
4988 public static int WaitAny(Task[] tasks, int millisecondsTimeout)
4990 return WaitAnyCore(tasks, millisecondsTimeout, default);
4993 /// <summary>
4994 /// Waits for any of the provided <see cref="Task"/> objects to complete execution.
4995 /// </summary>
4996 /// <param name="tasks">
4997 /// An array of <see cref="Task"/> instances on which to wait.
4998 /// </param>
4999 /// <param name="millisecondsTimeout">
5000 /// The number of milliseconds to wait, or <see cref="System.Threading.Timeout.Infinite"/> (-1) to
5001 /// wait indefinitely.
5002 /// </param>
5003 /// <param name="cancellationToken">
5004 /// A <see cref="CancellationToken"/> to observe while waiting for a task to complete.
5005 /// </param>
5006 /// <returns>
5007 /// The index of the completed task in the <paramref name="tasks"/> array argument, or -1 if the
5008 /// timeout occurred.
5009 /// </returns>
5010 /// <exception cref="System.ArgumentNullException">
5011 /// The <paramref name="tasks"/> argument is null.
5012 /// </exception>
5013 /// <exception cref="System.ArgumentException">
5014 /// The <paramref name="tasks"/> argument contains a null element.
5015 /// </exception>
5016 /// <exception cref="System.ArgumentOutOfRangeException">
5017 /// <paramref name="millisecondsTimeout"/> is a negative number other than -1, which represents an
5018 /// infinite time-out.
5019 /// </exception>
5020 /// <exception cref="System.OperationCanceledException">
5021 /// The <paramref name="cancellationToken"/> was canceled.
5022 /// </exception>
5023 [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
5024 public static int WaitAny(Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken) =>
5025 WaitAnyCore(tasks, millisecondsTimeout, cancellationToken);
5027 // Separated out to allow it to be optimized (caller is marked NoOptimization for VS parallel debugger
5028 // to be able to inspect arguments).
5029 private static int WaitAnyCore(Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken)
5031 if (tasks == null)
5033 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks);
5035 if (millisecondsTimeout < -1)
5037 ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.millisecondsTimeout);
5040 cancellationToken.ThrowIfCancellationRequested(); // early check before we make any allocations
5042 int signaledTaskIndex = -1;
5044 // Make a pass through the loop to check for any tasks that may have
5045 // already been completed, and to verify that no tasks are null.
5047 for (int taskIndex = 0; taskIndex < tasks.Length; taskIndex++)
5049 Task task = tasks[taskIndex];
5051 if (task == null)
5053 ThrowHelper.ThrowArgumentException(ExceptionResource.Task_WaitMulti_NullTask, ExceptionArgument.tasks);
5056 if (signaledTaskIndex == -1 && task.IsCompleted)
5058 // We found our first completed task. Store it, but we can't just return here,
5059 // as we still need to validate the whole array for nulls.
5060 signaledTaskIndex = taskIndex;
5064 if (signaledTaskIndex == -1 && tasks.Length != 0)
5066 Task<Task> firstCompleted = TaskFactory.CommonCWAnyLogic(tasks, isSyncBlocking: true);
5067 bool waitCompleted = firstCompleted.Wait(millisecondsTimeout, cancellationToken);
5068 if (waitCompleted)
5070 Debug.Assert(firstCompleted.Status == TaskStatus.RanToCompletion);
5071 signaledTaskIndex = Array.IndexOf(tasks, firstCompleted.Result);
5072 Debug.Assert(signaledTaskIndex >= 0);
5074 else
5076 TaskFactory.CommonCWAnyLogicCleanup(firstCompleted);
5080 // We need to prevent the tasks array from being GC'ed until we come out of the wait.
5081 // This is necessary so that the Parallel Debugger can traverse it during the long wait
5082 // and deduce waiter/waitee relationships
5083 GC.KeepAlive(tasks);
5085 // Return the index
5086 return signaledTaskIndex;
5089 #region FromResult / FromException / FromCanceled
5091 /// <summary>Creates a <see cref="Task{TResult}"/> that's completed successfully with the specified result.</summary>
5092 /// <typeparam name="TResult">The type of the result returned by the task.</typeparam>
5093 /// <param name="result">The result to store into the completed task.</param>
5094 /// <returns>The successfully completed task.</returns>
5095 public static Task<TResult> FromResult<TResult>(TResult result)
5097 return new Task<TResult>(result);
5100 /// <summary>Creates a <see cref="Task{TResult}"/> that's completed exceptionally with the specified exception.</summary>
5101 /// <param name="exception">The exception with which to complete the task.</param>
5102 /// <returns>The faulted task.</returns>
5103 public static Task FromException(Exception exception)
5105 if (exception == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exception);
5107 var task = new Task();
5108 bool succeeded = task.TrySetException(exception);
5109 Debug.Assert(succeeded, "This should always succeed on a new task.");
5110 return task;
5113 /// <summary>Creates a <see cref="Task{TResult}"/> that's completed exceptionally with the specified exception.</summary>
5114 /// <typeparam name="TResult">The type of the result returned by the task.</typeparam>
5115 /// <param name="exception">The exception with which to complete the task.</param>
5116 /// <returns>The faulted task.</returns>
5117 public static Task<TResult> FromException<TResult>(Exception exception)
5119 if (exception == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exception);
5121 var task = new Task<TResult>();
5122 bool succeeded = task.TrySetException(exception);
5123 Debug.Assert(succeeded, "This should always succeed on a new task.");
5124 return task;
5127 /// <summary>Creates a <see cref="Task"/> that's completed due to cancellation with the specified token.</summary>
5128 /// <param name="cancellationToken">The token with which to complete the task.</param>
5129 /// <returns>The canceled task.</returns>
5130 public static Task FromCanceled(CancellationToken cancellationToken)
5132 if (!cancellationToken.IsCancellationRequested)
5133 ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.cancellationToken);
5134 return new Task(true, TaskCreationOptions.None, cancellationToken);
5137 /// <summary>Creates a <see cref="Task{TResult}"/> that's completed due to cancellation with the specified token.</summary>
5138 /// <typeparam name="TResult">The type of the result returned by the task.</typeparam>
5139 /// <param name="cancellationToken">The token with which to complete the task.</param>
5140 /// <returns>The canceled task.</returns>
5141 public static Task<TResult> FromCanceled<TResult>(CancellationToken cancellationToken)
5143 if (!cancellationToken.IsCancellationRequested)
5144 ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.cancellationToken);
5145 return new Task<TResult>(true, default, TaskCreationOptions.None, cancellationToken);
5148 /// <summary>Creates a <see cref="Task"/> that's completed due to cancellation with the specified exception.</summary>
5149 /// <param name="exception">The exception with which to complete the task.</param>
5150 /// <returns>The canceled task.</returns>
5151 internal static Task FromCanceled(OperationCanceledException exception)
5153 Debug.Assert(exception != null);
5155 var task = new Task();
5156 bool succeeded = task.TrySetCanceled(exception.CancellationToken, exception);
5157 Debug.Assert(succeeded, "This should always succeed on a new task.");
5158 return task;
5161 /// <summary>Creates a <see cref="Task{TResult}"/> that's completed due to cancellation with the specified exception.</summary>
5162 /// <typeparam name="TResult">The type of the result returned by the task.</typeparam>
5163 /// <param name="exception">The exception with which to complete the task.</param>
5164 /// <returns>The canceled task.</returns>
5165 internal static Task<TResult> FromCanceled<TResult>(OperationCanceledException exception)
5167 Debug.Assert(exception != null);
5169 var task = new Task<TResult>();
5170 bool succeeded = task.TrySetCanceled(exception.CancellationToken, exception);
5171 Debug.Assert(succeeded, "This should always succeed on a new task.");
5172 return task;
5175 #endregion
5177 #region Run methods
5180 /// <summary>
5181 /// Queues the specified work to run on the ThreadPool and returns a Task handle for that work.
5182 /// </summary>
5183 /// <param name="action">The work to execute asynchronously</param>
5184 /// <returns>A Task that represents the work queued to execute in the ThreadPool.</returns>
5185 /// <exception cref="System.ArgumentNullException">
5186 /// The <paramref name="action"/> parameter was null.
5187 /// </exception>
5188 public static Task Run(Action action)
5190 return Task.InternalStartNew(null, action, null, default, TaskScheduler.Default,
5191 TaskCreationOptions.DenyChildAttach, InternalTaskOptions.None);
5194 /// <summary>
5195 /// Queues the specified work to run on the ThreadPool and returns a Task handle for that work.
5196 /// </summary>
5197 /// <param name="action">The work to execute asynchronously</param>
5198 /// <param name="cancellationToken">A cancellation token that should be used to cancel the work</param>
5199 /// <returns>A Task that represents the work queued to execute in the ThreadPool.</returns>
5200 /// <exception cref="System.ArgumentNullException">
5201 /// The <paramref name="action"/> parameter was null.
5202 /// </exception>
5203 /// <exception cref="System.ObjectDisposedException">
5204 /// The CancellationTokenSource associated with <paramref name="cancellationToken"/> was disposed.
5205 /// </exception>
5206 public static Task Run(Action action, CancellationToken cancellationToken)
5208 return Task.InternalStartNew(null, action, null, cancellationToken, TaskScheduler.Default,
5209 TaskCreationOptions.DenyChildAttach, InternalTaskOptions.None);
5212 /// <summary>
5213 /// Queues the specified work to run on the ThreadPool and returns a Task(TResult) handle for that work.
5214 /// </summary>
5215 /// <param name="function">The work to execute asynchronously</param>
5216 /// <returns>A Task(TResult) that represents the work queued to execute in the ThreadPool.</returns>
5217 /// <exception cref="System.ArgumentNullException">
5218 /// The <paramref name="function"/> parameter was null.
5219 /// </exception>
5220 public static Task<TResult> Run<TResult>(Func<TResult> function)
5222 return Task<TResult>.StartNew(null, function, default,
5223 TaskCreationOptions.DenyChildAttach, InternalTaskOptions.None, TaskScheduler.Default);
5226 /// <summary>
5227 /// Queues the specified work to run on the ThreadPool and returns a Task(TResult) handle for that work.
5228 /// </summary>
5229 /// <param name="function">The work to execute asynchronously</param>
5230 /// <param name="cancellationToken">A cancellation token that should be used to cancel the work</param>
5231 /// <returns>A Task(TResult) that represents the work queued to execute in the ThreadPool.</returns>
5232 /// <exception cref="System.ArgumentNullException">
5233 /// The <paramref name="function"/> parameter was null.
5234 /// </exception>
5235 /// <exception cref="System.ObjectDisposedException">
5236 /// The CancellationTokenSource associated with <paramref name="cancellationToken"/> was disposed.
5237 /// </exception>
5238 public static Task<TResult> Run<TResult>(Func<TResult> function, CancellationToken cancellationToken)
5240 return Task<TResult>.StartNew(null, function, cancellationToken,
5241 TaskCreationOptions.DenyChildAttach, InternalTaskOptions.None, TaskScheduler.Default);
5244 /// <summary>
5245 /// Queues the specified work to run on the ThreadPool and returns a proxy for the
5246 /// Task returned by <paramref name="function"/>.
5247 /// </summary>
5248 /// <param name="function">The work to execute asynchronously</param>
5249 /// <returns>A Task that represents a proxy for the Task returned by <paramref name="function"/>.</returns>
5250 /// <exception cref="System.ArgumentNullException">
5251 /// The <paramref name="function"/> parameter was null.
5252 /// </exception>
5253 public static Task Run(Func<Task?> function)
5255 return Run(function, default);
5259 /// <summary>
5260 /// Queues the specified work to run on the ThreadPool and returns a proxy for the
5261 /// Task returned by <paramref name="function"/>.
5262 /// </summary>
5263 /// <param name="function">The work to execute asynchronously</param>
5264 /// <param name="cancellationToken">A cancellation token that should be used to cancel the work</param>
5265 /// <returns>A Task that represents a proxy for the Task returned by <paramref name="function"/>.</returns>
5266 /// <exception cref="System.ArgumentNullException">
5267 /// The <paramref name="function"/> parameter was null.
5268 /// </exception>
5269 /// <exception cref="System.ObjectDisposedException">
5270 /// The CancellationTokenSource associated with <paramref name="cancellationToken"/> was disposed.
5271 /// </exception>
5272 public static Task Run(Func<Task?> function, CancellationToken cancellationToken)
5274 // Check arguments
5275 if (function == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.function);
5277 // Short-circuit if we are given a pre-canceled token
5278 if (cancellationToken.IsCancellationRequested)
5279 return Task.FromCanceled(cancellationToken);
5281 // Kick off initial Task, which will call the user-supplied function and yield a Task.
5282 Task<Task?> task1 = Task<Task?>.Factory.StartNew(function, cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
5284 // Create a promise-style Task to be used as a proxy for the operation
5285 // Set lookForOce == true so that unwrap logic can be on the lookout for OCEs thrown as faults from task1, to support in-delegate cancellation.
5286 UnwrapPromise<VoidTaskResult> promise = new UnwrapPromise<VoidTaskResult>(task1, lookForOce: true);
5288 return promise;
5291 /// <summary>
5292 /// Queues the specified work to run on the ThreadPool and returns a proxy for the
5293 /// Task(TResult) returned by <paramref name="function"/>.
5294 /// </summary>
5295 /// <typeparam name="TResult">The type of the result returned by the proxy Task.</typeparam>
5296 /// <param name="function">The work to execute asynchronously</param>
5297 /// <returns>A Task(TResult) that represents a proxy for the Task(TResult) returned by <paramref name="function"/>.</returns>
5298 /// <exception cref="System.ArgumentNullException">
5299 /// The <paramref name="function"/> parameter was null.
5300 /// </exception>
5301 public static Task<TResult> Run<TResult>(Func<Task<TResult>?> function)
5303 return Run(function, default);
5306 /// <summary>
5307 /// Queues the specified work to run on the ThreadPool and returns a proxy for the
5308 /// Task(TResult) returned by <paramref name="function"/>.
5309 /// </summary>
5310 /// <typeparam name="TResult">The type of the result returned by the proxy Task.</typeparam>
5311 /// <param name="function">The work to execute asynchronously</param>
5312 /// <param name="cancellationToken">A cancellation token that should be used to cancel the work</param>
5313 /// <returns>A Task(TResult) that represents a proxy for the Task(TResult) returned by <paramref name="function"/>.</returns>
5314 /// <exception cref="System.ArgumentNullException">
5315 /// The <paramref name="function"/> parameter was null.
5316 /// </exception>
5317 public static Task<TResult> Run<TResult>(Func<Task<TResult>?> function, CancellationToken cancellationToken)
5319 // Check arguments
5320 if (function == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.function);
5322 // Short-circuit if we are given a pre-canceled token
5323 if (cancellationToken.IsCancellationRequested)
5324 return Task.FromCanceled<TResult>(cancellationToken);
5326 // Kick off initial Task, which will call the user-supplied function and yield a Task.
5327 Task<Task<TResult>?> task1 = Task<Task<TResult>?>.Factory.StartNew(function, cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
5329 // Create a promise-style Task to be used as a proxy for the operation
5330 // Set lookForOce == true so that unwrap logic can be on the lookout for OCEs thrown as faults from task1, to support in-delegate cancellation.
5331 UnwrapPromise<TResult> promise = new UnwrapPromise<TResult>(task1, lookForOce: true);
5333 return promise;
5337 #endregion
5339 #region Delay methods
5341 /// <summary>
5342 /// Creates a Task that will complete after a time delay.
5343 /// </summary>
5344 /// <param name="delay">The time span to wait before completing the returned Task</param>
5345 /// <returns>A Task that represents the time delay</returns>
5346 /// <exception cref="System.ArgumentOutOfRangeException">
5347 /// The <paramref name="delay"/> is less than -1 or greater than int.MaxValue.
5348 /// </exception>
5349 /// <remarks>
5350 /// After the specified time delay, the Task is completed in RanToCompletion state.
5351 /// </remarks>
5352 public static Task Delay(TimeSpan delay)
5354 return Delay(delay, default);
5357 /// <summary>
5358 /// Creates a Task that will complete after a time delay.
5359 /// </summary>
5360 /// <param name="delay">The time span to wait before completing the returned Task</param>
5361 /// <param name="cancellationToken">The cancellation token that will be checked prior to completing the returned Task</param>
5362 /// <returns>A Task that represents the time delay</returns>
5363 /// <exception cref="System.ArgumentOutOfRangeException">
5364 /// The <paramref name="delay"/> is less than -1 or greater than int.MaxValue.
5365 /// </exception>
5366 /// <exception cref="System.ObjectDisposedException">
5367 /// The provided <paramref name="cancellationToken"/> has already been disposed.
5368 /// </exception>
5369 /// <remarks>
5370 /// If the cancellation token is signaled before the specified time delay, then the Task is completed in
5371 /// Canceled state. Otherwise, the Task is completed in RanToCompletion state once the specified time
5372 /// delay has expired.
5373 /// </remarks>
5374 public static Task Delay(TimeSpan delay, CancellationToken cancellationToken)
5376 long totalMilliseconds = (long)delay.TotalMilliseconds;
5377 if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue)
5379 ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.delay, ExceptionResource.Task_Delay_InvalidDelay);
5382 return Delay((int)totalMilliseconds, cancellationToken);
5385 /// <summary>
5386 /// Creates a Task that will complete after a time delay.
5387 /// </summary>
5388 /// <param name="millisecondsDelay">The number of milliseconds to wait before completing the returned Task</param>
5389 /// <returns>A Task that represents the time delay</returns>
5390 /// <exception cref="System.ArgumentOutOfRangeException">
5391 /// The <paramref name="millisecondsDelay"/> is less than -1.
5392 /// </exception>
5393 /// <remarks>
5394 /// After the specified time delay, the Task is completed in RanToCompletion state.
5395 /// </remarks>
5396 public static Task Delay(int millisecondsDelay)
5398 return Delay(millisecondsDelay, default);
5401 /// <summary>
5402 /// Creates a Task that will complete after a time delay.
5403 /// </summary>
5404 /// <param name="millisecondsDelay">The number of milliseconds to wait before completing the returned Task</param>
5405 /// <param name="cancellationToken">The cancellation token that will be checked prior to completing the returned Task</param>
5406 /// <returns>A Task that represents the time delay</returns>
5407 /// <exception cref="System.ArgumentOutOfRangeException">
5408 /// The <paramref name="millisecondsDelay"/> is less than -1.
5409 /// </exception>
5410 /// <exception cref="System.ObjectDisposedException">
5411 /// The provided <paramref name="cancellationToken"/> has already been disposed.
5412 /// </exception>
5413 /// <remarks>
5414 /// If the cancellation token is signaled before the specified time delay, then the Task is completed in
5415 /// Canceled state. Otherwise, the Task is completed in RanToCompletion state once the specified time
5416 /// delay has expired.
5417 /// </remarks>
5418 public static Task Delay(int millisecondsDelay, CancellationToken cancellationToken)
5420 // Throw on non-sensical time
5421 if (millisecondsDelay < -1)
5423 ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.millisecondsDelay, ExceptionResource.Task_Delay_InvalidMillisecondsDelay);
5426 return
5427 cancellationToken.IsCancellationRequested ? FromCanceled(cancellationToken) :
5428 millisecondsDelay == 0 ? CompletedTask :
5429 cancellationToken.CanBeCanceled ? new DelayPromiseWithCancellation(millisecondsDelay, cancellationToken) :
5430 new DelayPromise(millisecondsDelay);
5433 /// <summary>Task that also stores the completion closure and logic for Task.Delay implementation.</summary>
5434 private class DelayPromise : Task
5436 private readonly TimerQueueTimer? _timer;
5438 internal DelayPromise(int millisecondsDelay)
5440 Debug.Assert(millisecondsDelay != 0);
5442 if (AsyncCausalityTracer.LoggingOn)
5443 AsyncCausalityTracer.TraceOperationCreation(this, "Task.Delay");
5445 if (s_asyncDebuggingEnabled)
5446 AddToActiveTasks(this);
5448 if (millisecondsDelay != Timeout.Infinite) // no need to create the timer if it's an infinite timeout
5450 _timer = new TimerQueueTimer(state => ((DelayPromise)state!).CompleteTimedOut(), this, (uint)millisecondsDelay, Timeout.UnsignedInfinite, flowExecutionContext: false);
5451 if (IsCanceled)
5453 // Handle rare race condition where cancellation occurs prior to our having created and stored the timer, in which case
5454 // the timer won't have been cleaned up appropriately. This call to close might race with the Cleanup call to Close,
5455 // but Close is thread-safe and will be a nop if it's already been closed.
5456 _timer.Close();
5461 private void CompleteTimedOut()
5463 if (TrySetResult())
5465 Cleanup();
5467 if (s_asyncDebuggingEnabled)
5468 RemoveFromActiveTasks(this);
5470 if (AsyncCausalityTracer.LoggingOn)
5471 AsyncCausalityTracer.TraceOperationCompletion(this, AsyncCausalityStatus.Completed);
5475 protected virtual void Cleanup() => _timer?.Close();
5478 /// <summary>DelayPromise that also supports cancellation.</summary>
5479 private sealed class DelayPromiseWithCancellation : DelayPromise
5481 private readonly CancellationToken _token;
5482 private readonly CancellationTokenRegistration _registration;
5484 internal DelayPromiseWithCancellation(int millisecondsDelay, CancellationToken token) : base(millisecondsDelay)
5486 Debug.Assert(token.CanBeCanceled);
5488 _token = token;
5489 _registration = token.UnsafeRegister(state => ((DelayPromiseWithCancellation)state!).CompleteCanceled(), this);
5492 private void CompleteCanceled()
5494 if (TrySetCanceled(_token))
5496 Cleanup();
5497 // This path doesn't invoke RemoveFromActiveTasks or TraceOperationCompletion
5498 // because that's strangely already handled inside of TrySetCanceled.
5502 protected override void Cleanup()
5504 _registration.Dispose();
5505 base.Cleanup();
5508 #endregion
5510 #region WhenAll
5511 /// <summary>
5512 /// Creates a task that will complete when all of the supplied tasks have completed.
5513 /// </summary>
5514 /// <param name="tasks">The tasks to wait on for completion.</param>
5515 /// <returns>A task that represents the completion of all of the supplied tasks.</returns>
5516 /// <remarks>
5517 /// <para>
5518 /// If any of the supplied tasks completes in a faulted state, the returned task will also complete in a Faulted state,
5519 /// where its exceptions will contain the aggregation of the set of unwrapped exceptions from each of the supplied tasks.
5520 /// </para>
5521 /// <para>
5522 /// If none of the supplied tasks faulted but at least one of them was canceled, the returned task will end in the Canceled state.
5523 /// </para>
5524 /// <para>
5525 /// If none of the tasks faulted and none of the tasks were canceled, the resulting task will end in the RanToCompletion state.
5526 /// </para>
5527 /// <para>
5528 /// If the supplied array/enumerable contains no tasks, the returned task will immediately transition to a RanToCompletion
5529 /// state before it's returned to the caller.
5530 /// </para>
5531 /// </remarks>
5532 /// <exception cref="System.ArgumentNullException">
5533 /// The <paramref name="tasks"/> argument was null.
5534 /// </exception>
5535 /// <exception cref="System.ArgumentException">
5536 /// The <paramref name="tasks"/> collection contained a null task.
5537 /// </exception>
5538 public static Task WhenAll(IEnumerable<Task> tasks)
5540 // Take a more efficient path if tasks is actually an array
5541 if (tasks is Task[] taskArray)
5543 return WhenAll(taskArray);
5546 // Skip a List allocation/copy if tasks is a collection
5547 if (tasks is ICollection<Task> taskCollection)
5549 int index = 0;
5550 taskArray = new Task[taskCollection.Count];
5551 foreach (Task task in tasks)
5553 if (task == null) ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_NullTask, ExceptionArgument.tasks);
5554 taskArray[index++] = task;
5556 return InternalWhenAll(taskArray);
5559 // Do some argument checking and convert tasks to a List (and later an array).
5560 if (tasks == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks);
5561 List<Task> taskList = new List<Task>();
5562 foreach (Task task in tasks)
5564 if (task == null) ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_NullTask, ExceptionArgument.tasks);
5565 taskList.Add(task);
5568 // Delegate the rest to InternalWhenAll()
5569 return InternalWhenAll(taskList.ToArray());
5572 /// <summary>
5573 /// Creates a task that will complete when all of the supplied tasks have completed.
5574 /// </summary>
5575 /// <param name="tasks">The tasks to wait on for completion.</param>
5576 /// <returns>A task that represents the completion of all of the supplied tasks.</returns>
5577 /// <remarks>
5578 /// <para>
5579 /// If any of the supplied tasks completes in a faulted state, the returned task will also complete in a Faulted state,
5580 /// where its exceptions will contain the aggregation of the set of unwrapped exceptions from each of the supplied tasks.
5581 /// </para>
5582 /// <para>
5583 /// If none of the supplied tasks faulted but at least one of them was canceled, the returned task will end in the Canceled state.
5584 /// </para>
5585 /// <para>
5586 /// If none of the tasks faulted and none of the tasks were canceled, the resulting task will end in the RanToCompletion state.
5587 /// </para>
5588 /// <para>
5589 /// If the supplied array/enumerable contains no tasks, the returned task will immediately transition to a RanToCompletion
5590 /// state before it's returned to the caller.
5591 /// </para>
5592 /// </remarks>
5593 /// <exception cref="System.ArgumentNullException">
5594 /// The <paramref name="tasks"/> argument was null.
5595 /// </exception>
5596 /// <exception cref="System.ArgumentException">
5597 /// The <paramref name="tasks"/> array contained a null task.
5598 /// </exception>
5599 public static Task WhenAll(params Task[] tasks)
5601 // Do some argument checking and make a defensive copy of the tasks array
5602 if (tasks == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks);
5604 int taskCount = tasks.Length;
5605 if (taskCount == 0) return InternalWhenAll(tasks); // Small optimization in the case of an empty array.
5607 Task[] tasksCopy = new Task[taskCount];
5608 for (int i = 0; i < taskCount; i++)
5610 Task task = tasks[i];
5611 if (task == null) ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_NullTask, ExceptionArgument.tasks);
5612 tasksCopy[i] = task;
5615 // The rest can be delegated to InternalWhenAll()
5616 return InternalWhenAll(tasksCopy);
5619 // Some common logic to support WhenAll() methods
5620 // tasks should be a defensive copy.
5621 private static Task InternalWhenAll(Task[] tasks)
5623 Debug.Assert(tasks != null, "Expected a non-null tasks array");
5624 return (tasks.Length == 0) ? // take shortcut if there are no tasks upon which to wait
5625 Task.CompletedTask :
5626 new WhenAllPromise(tasks);
5629 // A Task that gets completed when all of its constituent tasks complete.
5630 // Completion logic will analyze the antecedents in order to choose completion status.
5631 // This type allows us to replace this logic:
5632 // Task promise = new Task(...);
5633 // Action<Task> completionAction = delegate { <completion logic>};
5634 // TaskFactory.CommonCWAllLogic(tasksCopy).AddCompletionAction(completionAction);
5635 // return promise;
5636 // which involves several allocations, with this logic:
5637 // return new WhenAllPromise(tasksCopy);
5638 // which saves a couple of allocations and enables debugger notification specialization.
5640 // Used in InternalWhenAll(Task[])
5641 private sealed class WhenAllPromise : Task, ITaskCompletionAction
5643 /// <summary>
5644 /// Stores all of the constituent tasks. Tasks clear themselves out of this
5645 /// array as they complete, but only if they don't have their wait notification bit set.
5646 /// </summary>
5647 private readonly Task?[] m_tasks;
5648 /// <summary>The number of tasks remaining to complete.</summary>
5649 private int m_count;
5651 internal WhenAllPromise(Task[] tasks)
5653 Debug.Assert(tasks != null, "Expected a non-null task array");
5654 Debug.Assert(tasks.Length > 0, "Expected a non-zero length task array");
5656 if (AsyncCausalityTracer.LoggingOn)
5657 AsyncCausalityTracer.TraceOperationCreation(this, "Task.WhenAll");
5659 if (s_asyncDebuggingEnabled)
5660 AddToActiveTasks(this);
5662 m_tasks = tasks;
5663 m_count = tasks.Length;
5665 foreach (Task task in tasks)
5667 if (task.IsCompleted) this.Invoke(task); // short-circuit the completion action, if possible
5668 else task.AddCompletionAction(this); // simple completion action
5672 public void Invoke(Task completedTask)
5674 if (AsyncCausalityTracer.LoggingOn)
5675 AsyncCausalityTracer.TraceOperationRelation(this, CausalityRelation.Join);
5677 // Decrement the count, and only continue to complete the promise if we're the last one.
5678 if (Interlocked.Decrement(ref m_count) == 0)
5680 // Set up some accounting variables
5681 List<ExceptionDispatchInfo>? observedExceptions = null;
5682 Task? canceledTask = null;
5684 // Loop through antecedents:
5685 // If any one of them faults, the result will be faulted
5686 // If none fault, but at least one is canceled, the result will be canceled
5687 // If none fault or are canceled, then result will be RanToCompletion
5688 for (int i = 0; i < m_tasks.Length; i++)
5690 Task? task = m_tasks[i];
5691 Debug.Assert(task != null, "Constituent task in WhenAll should never be null");
5693 if (task.IsFaulted)
5695 if (observedExceptions == null) observedExceptions = new List<ExceptionDispatchInfo>();
5696 observedExceptions.AddRange(task.GetExceptionDispatchInfos());
5698 else if (task.IsCanceled)
5700 if (canceledTask == null) canceledTask = task; // use the first task that's canceled
5703 // Regardless of completion state, if the task has its debug bit set, transfer it to the
5704 // WhenAll task. We must do this before we complete the task.
5705 if (task.IsWaitNotificationEnabled) this.SetNotificationForWaitCompletion(enabled: true);
5706 else m_tasks[i] = null; // avoid holding onto tasks unnecessarily
5709 if (observedExceptions != null)
5711 Debug.Assert(observedExceptions.Count > 0, "Expected at least one exception");
5713 //We don't need to TraceOperationCompleted here because TrySetException will call Finish and we'll log it there
5715 TrySetException(observedExceptions);
5717 else if (canceledTask != null)
5719 TrySetCanceled(canceledTask.CancellationToken, canceledTask.GetCancellationExceptionDispatchInfo());
5721 else
5723 if (AsyncCausalityTracer.LoggingOn)
5724 AsyncCausalityTracer.TraceOperationCompletion(this, AsyncCausalityStatus.Completed);
5726 if (s_asyncDebuggingEnabled)
5727 RemoveFromActiveTasks(this);
5729 TrySetResult();
5732 Debug.Assert(m_count >= 0, "Count should never go below 0");
5735 public bool InvokeMayRunArbitraryCode => true;
5737 /// <summary>
5738 /// Returns whether we should notify the debugger of a wait completion. This returns
5739 /// true iff at least one constituent task has its bit set.
5740 /// </summary>
5741 internal override bool ShouldNotifyDebuggerOfWaitCompletion =>
5742 base.ShouldNotifyDebuggerOfWaitCompletion &&
5743 AnyTaskRequiresNotifyDebuggerOfWaitCompletion(m_tasks);
5746 /// <summary>
5747 /// Creates a task that will complete when all of the supplied tasks have completed.
5748 /// </summary>
5749 /// <param name="tasks">The tasks to wait on for completion.</param>
5750 /// <returns>A task that represents the completion of all of the supplied tasks.</returns>
5751 /// <remarks>
5752 /// <para>
5753 /// If any of the supplied tasks completes in a faulted state, the returned task will also complete in a Faulted state,
5754 /// where its exceptions will contain the aggregation of the set of unwrapped exceptions from each of the supplied tasks.
5755 /// </para>
5756 /// <para>
5757 /// If none of the supplied tasks faulted but at least one of them was canceled, the returned task will end in the Canceled state.
5758 /// </para>
5759 /// <para>
5760 /// If none of the tasks faulted and none of the tasks were canceled, the resulting task will end in the RanToCompletion state.
5761 /// The Result of the returned task will be set to an array containing all of the results of the
5762 /// supplied tasks in the same order as they were provided (e.g. if the input tasks array contained t1, t2, t3, the output
5763 /// task's Result will return an TResult[] where arr[0] == t1.Result, arr[1] == t2.Result, and arr[2] == t3.Result).
5764 /// </para>
5765 /// <para>
5766 /// If the supplied array/enumerable contains no tasks, the returned task will immediately transition to a RanToCompletion
5767 /// state before it's returned to the caller. The returned TResult[] will be an array of 0 elements.
5768 /// </para>
5769 /// </remarks>
5770 /// <exception cref="System.ArgumentNullException">
5771 /// The <paramref name="tasks"/> argument was null.
5772 /// </exception>
5773 /// <exception cref="System.ArgumentException">
5774 /// The <paramref name="tasks"/> collection contained a null task.
5775 /// </exception>
5776 public static Task<TResult[]> WhenAll<TResult>(IEnumerable<Task<TResult>> tasks)
5778 // Take a more efficient route if tasks is actually an array
5779 if (tasks is Task<TResult>[] taskArray)
5781 return WhenAll<TResult>(taskArray);
5784 // Skip a List allocation/copy if tasks is a collection
5785 if (tasks is ICollection<Task<TResult>> taskCollection)
5787 int index = 0;
5788 taskArray = new Task<TResult>[taskCollection.Count];
5789 foreach (Task<TResult> task in tasks)
5791 if (task == null) ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_NullTask, ExceptionArgument.tasks);
5792 taskArray[index++] = task;
5794 return InternalWhenAll<TResult>(taskArray);
5797 // Do some argument checking and convert tasks into a List (later an array)
5798 if (tasks == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks);
5799 List<Task<TResult>> taskList = new List<Task<TResult>>();
5800 foreach (Task<TResult> task in tasks)
5802 if (task == null) ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_NullTask, ExceptionArgument.tasks);
5803 taskList.Add(task);
5806 // Delegate the rest to InternalWhenAll<TResult>().
5807 return InternalWhenAll<TResult>(taskList.ToArray());
5810 /// <summary>
5811 /// Creates a task that will complete when all of the supplied tasks have completed.
5812 /// </summary>
5813 /// <param name="tasks">The tasks to wait on for completion.</param>
5814 /// <returns>A task that represents the completion of all of the supplied tasks.</returns>
5815 /// <remarks>
5816 /// <para>
5817 /// If any of the supplied tasks completes in a faulted state, the returned task will also complete in a Faulted state,
5818 /// where its exceptions will contain the aggregation of the set of unwrapped exceptions from each of the supplied tasks.
5819 /// </para>
5820 /// <para>
5821 /// If none of the supplied tasks faulted but at least one of them was canceled, the returned task will end in the Canceled state.
5822 /// </para>
5823 /// <para>
5824 /// If none of the tasks faulted and none of the tasks were canceled, the resulting task will end in the RanToCompletion state.
5825 /// The Result of the returned task will be set to an array containing all of the results of the
5826 /// supplied tasks in the same order as they were provided (e.g. if the input tasks array contained t1, t2, t3, the output
5827 /// task's Result will return an TResult[] where arr[0] == t1.Result, arr[1] == t2.Result, and arr[2] == t3.Result).
5828 /// </para>
5829 /// <para>
5830 /// If the supplied array/enumerable contains no tasks, the returned task will immediately transition to a RanToCompletion
5831 /// state before it's returned to the caller. The returned TResult[] will be an array of 0 elements.
5832 /// </para>
5833 /// </remarks>
5834 /// <exception cref="System.ArgumentNullException">
5835 /// The <paramref name="tasks"/> argument was null.
5836 /// </exception>
5837 /// <exception cref="System.ArgumentException">
5838 /// The <paramref name="tasks"/> array contained a null task.
5839 /// </exception>
5840 public static Task<TResult[]> WhenAll<TResult>(params Task<TResult>[] tasks)
5842 // Do some argument checking and make a defensive copy of the tasks array
5843 if (tasks == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks);
5845 int taskCount = tasks.Length;
5846 if (taskCount == 0) return InternalWhenAll<TResult>(tasks); // small optimization in the case of an empty task array
5848 Task<TResult>[] tasksCopy = new Task<TResult>[taskCount];
5849 for (int i = 0; i < taskCount; i++)
5851 Task<TResult> task = tasks[i];
5852 if (task == null) ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_NullTask, ExceptionArgument.tasks);
5853 tasksCopy[i] = task;
5856 // Delegate the rest to InternalWhenAll<TResult>()
5857 return InternalWhenAll<TResult>(tasksCopy);
5860 // Some common logic to support WhenAll<TResult> methods
5861 private static Task<TResult[]> InternalWhenAll<TResult>(Task<TResult>[] tasks)
5863 Debug.Assert(tasks != null, "Expected a non-null tasks array");
5864 return (tasks.Length == 0) ? // take shortcut if there are no tasks upon which to wait
5865 new Task<TResult[]>(false, Array.Empty<TResult>(), TaskCreationOptions.None, default) :
5866 new WhenAllPromise<TResult>(tasks);
5869 // A Task<T> that gets completed when all of its constituent tasks complete.
5870 // Completion logic will analyze the antecedents in order to choose completion status.
5871 // See comments for non-generic version of WhenAllPromise class.
5873 // Used in InternalWhenAll<TResult>(Task<TResult>[])
5874 private sealed class WhenAllPromise<T> : Task<T[]>, ITaskCompletionAction
5876 /// <summary>
5877 /// Stores all of the constituent tasks. Tasks clear themselves out of this
5878 /// array as they complete, but only if they don't have their wait notification bit set.
5879 /// </summary>
5880 private readonly Task<T>?[] m_tasks;
5881 /// <summary>The number of tasks remaining to complete.</summary>
5882 private int m_count;
5884 internal WhenAllPromise(Task<T>[] tasks) :
5885 base()
5887 Debug.Assert(tasks != null, "Expected a non-null task array");
5888 Debug.Assert(tasks.Length > 0, "Expected a non-zero length task array");
5890 m_tasks = tasks;
5891 m_count = tasks.Length;
5893 if (AsyncCausalityTracer.LoggingOn)
5894 AsyncCausalityTracer.TraceOperationCreation(this, "Task.WhenAll");
5896 if (s_asyncDebuggingEnabled)
5897 AddToActiveTasks(this);
5899 foreach (Task<T> task in tasks)
5901 if (task.IsCompleted) this.Invoke(task); // short-circuit the completion action, if possible
5902 else task.AddCompletionAction(this); // simple completion action
5906 public void Invoke(Task ignored)
5908 if (AsyncCausalityTracer.LoggingOn)
5909 AsyncCausalityTracer.TraceOperationRelation(this, CausalityRelation.Join);
5911 // Decrement the count, and only continue to complete the promise if we're the last one.
5912 if (Interlocked.Decrement(ref m_count) == 0)
5914 // Set up some accounting variables
5915 T[] results = new T[m_tasks.Length];
5916 List<ExceptionDispatchInfo>? observedExceptions = null;
5917 Task? canceledTask = null;
5919 // Loop through antecedents:
5920 // If any one of them faults, the result will be faulted
5921 // If none fault, but at least one is canceled, the result will be canceled
5922 // If none fault or are canceled, then result will be RanToCompletion
5923 for (int i = 0; i < m_tasks.Length; i++)
5925 Task<T>? task = m_tasks[i];
5926 Debug.Assert(task != null, "Constituent task in WhenAll should never be null");
5928 if (task.IsFaulted)
5930 if (observedExceptions == null) observedExceptions = new List<ExceptionDispatchInfo>();
5931 observedExceptions.AddRange(task.GetExceptionDispatchInfos());
5933 else if (task.IsCanceled)
5935 if (canceledTask == null) canceledTask = task; // use the first task that's canceled
5937 else
5939 Debug.Assert(task.Status == TaskStatus.RanToCompletion);
5940 results[i] = task.GetResultCore(waitCompletionNotification: false); // avoid Result, which would triggering debug notification
5943 // Regardless of completion state, if the task has its debug bit set, transfer it to the
5944 // WhenAll task. We must do this before we complete the task.
5945 if (task.IsWaitNotificationEnabled) this.SetNotificationForWaitCompletion(enabled: true);
5946 else m_tasks[i] = null; // avoid holding onto tasks unnecessarily
5949 if (observedExceptions != null)
5951 Debug.Assert(observedExceptions.Count > 0, "Expected at least one exception");
5953 //We don't need to TraceOperationCompleted here because TrySetException will call Finish and we'll log it there
5955 TrySetException(observedExceptions);
5957 else if (canceledTask != null)
5959 TrySetCanceled(canceledTask.CancellationToken, canceledTask.GetCancellationExceptionDispatchInfo());
5961 else
5963 if (AsyncCausalityTracer.LoggingOn)
5964 AsyncCausalityTracer.TraceOperationCompletion(this, AsyncCausalityStatus.Completed);
5966 if (Task.s_asyncDebuggingEnabled)
5967 RemoveFromActiveTasks(this);
5969 TrySetResult(results);
5972 Debug.Assert(m_count >= 0, "Count should never go below 0");
5975 public bool InvokeMayRunArbitraryCode => true;
5977 /// <summary>
5978 /// Returns whether we should notify the debugger of a wait completion. This returns true
5979 /// iff at least one constituent task has its bit set.
5980 /// </summary>
5981 internal override bool ShouldNotifyDebuggerOfWaitCompletion =>
5982 base.ShouldNotifyDebuggerOfWaitCompletion &&
5983 AnyTaskRequiresNotifyDebuggerOfWaitCompletion(m_tasks);
5985 #endregion
5987 #region WhenAny
5988 /// <summary>
5989 /// Creates a task that will complete when any of the supplied tasks have completed.
5990 /// </summary>
5991 /// <param name="tasks">The tasks to wait on for completion.</param>
5992 /// <returns>A task that represents the completion of one of the supplied tasks. The return Task's Result is the task that completed.</returns>
5993 /// <remarks>
5994 /// The returned task will complete when any of the supplied tasks has completed. The returned task will always end in the RanToCompletion state
5995 /// with its Result set to the first task to complete. This is true even if the first task to complete ended in the Canceled or Faulted state.
5996 /// </remarks>
5997 /// <exception cref="System.ArgumentNullException">
5998 /// The <paramref name="tasks"/> argument was null.
5999 /// </exception>
6000 /// <exception cref="System.ArgumentException">
6001 /// The <paramref name="tasks"/> array contained a null task, or was empty.
6002 /// </exception>
6003 public static Task<Task> WhenAny(params Task[] tasks)
6005 if (tasks == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks);
6006 if (tasks.Length == 0)
6008 ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_EmptyTaskList, ExceptionArgument.tasks);
6011 // Make a defensive copy, as the user may manipulate the tasks array
6012 // after we return but before the WhenAny asynchronously completes.
6013 int taskCount = tasks.Length;
6014 Task[] tasksCopy = new Task[taskCount];
6015 for (int i = 0; i < taskCount; i++)
6017 Task task = tasks[i];
6018 if (task == null) ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_NullTask, ExceptionArgument.tasks);
6019 tasksCopy[i] = task;
6022 // Previously implemented CommonCWAnyLogic() can handle the rest
6023 return TaskFactory.CommonCWAnyLogic(tasksCopy);
6026 /// <summary>
6027 /// Creates a task that will complete when any of the supplied tasks have completed.
6028 /// </summary>
6029 /// <param name="tasks">The tasks to wait on for completion.</param>
6030 /// <returns>A task that represents the completion of one of the supplied tasks. The return Task's Result is the task that completed.</returns>
6031 /// <remarks>
6032 /// The returned task will complete when any of the supplied tasks has completed. The returned task will always end in the RanToCompletion state
6033 /// with its Result set to the first task to complete. This is true even if the first task to complete ended in the Canceled or Faulted state.
6034 /// </remarks>
6035 /// <exception cref="System.ArgumentNullException">
6036 /// The <paramref name="tasks"/> argument was null.
6037 /// </exception>
6038 /// <exception cref="System.ArgumentException">
6039 /// The <paramref name="tasks"/> collection contained a null task, or was empty.
6040 /// </exception>
6041 public static Task<Task> WhenAny(IEnumerable<Task> tasks)
6043 if (tasks == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks);
6045 // Make a defensive copy, as the user may manipulate the tasks collection
6046 // after we return but before the WhenAny asynchronously completes.
6047 List<Task> taskList = new List<Task>();
6048 foreach (Task task in tasks)
6050 if (task == null) ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_NullTask, ExceptionArgument.tasks);
6051 taskList.Add(task);
6054 if (taskList.Count == 0)
6056 ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_EmptyTaskList, ExceptionArgument.tasks);
6059 // Previously implemented CommonCWAnyLogic() can handle the rest
6060 return TaskFactory.CommonCWAnyLogic(taskList);
6063 /// <summary>
6064 /// Creates a task that will complete when any of the supplied tasks have completed.
6065 /// </summary>
6066 /// <param name="tasks">The tasks to wait on for completion.</param>
6067 /// <returns>A task that represents the completion of one of the supplied tasks. The return Task's Result is the task that completed.</returns>
6068 /// <remarks>
6069 /// The returned task will complete when any of the supplied tasks has completed. The returned task will always end in the RanToCompletion state
6070 /// with its Result set to the first task to complete. This is true even if the first task to complete ended in the Canceled or Faulted state.
6071 /// </remarks>
6072 /// <exception cref="System.ArgumentNullException">
6073 /// The <paramref name="tasks"/> argument was null.
6074 /// </exception>
6075 /// <exception cref="System.ArgumentException">
6076 /// The <paramref name="tasks"/> array contained a null task, or was empty.
6077 /// </exception>
6078 public static Task<Task<TResult>> WhenAny<TResult>(params Task<TResult>[] tasks)
6080 // We would just like to do this:
6081 // return (Task<Task<TResult>>) WhenAny( (Task[]) tasks);
6082 // but classes are not covariant to enable casting Task<TResult> to Task<Task<TResult>>.
6084 // Call WhenAny(Task[]) for basic functionality
6085 Task<Task> intermediate = WhenAny((Task[])tasks);
6087 // Return a continuation task with the correct result type
6088 return intermediate.ContinueWith(Task<TResult>.TaskWhenAnyCast.Value, default,
6089 TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default);
6092 /// <summary>
6093 /// Creates a task that will complete when any of the supplied tasks have completed.
6094 /// </summary>
6095 /// <param name="tasks">The tasks to wait on for completion.</param>
6096 /// <returns>A task that represents the completion of one of the supplied tasks. The return Task's Result is the task that completed.</returns>
6097 /// <remarks>
6098 /// The returned task will complete when any of the supplied tasks has completed. The returned task will always end in the RanToCompletion state
6099 /// with its Result set to the first task to complete. This is true even if the first task to complete ended in the Canceled or Faulted state.
6100 /// </remarks>
6101 /// <exception cref="System.ArgumentNullException">
6102 /// The <paramref name="tasks"/> argument was null.
6103 /// </exception>
6104 /// <exception cref="System.ArgumentException">
6105 /// The <paramref name="tasks"/> collection contained a null task, or was empty.
6106 /// </exception>
6107 public static Task<Task<TResult>> WhenAny<TResult>(IEnumerable<Task<TResult>> tasks)
6109 // We would just like to do this:
6110 // return (Task<Task<TResult>>) WhenAny( (IEnumerable<Task>) tasks);
6111 // but classes are not covariant to enable casting Task<TResult> to Task<Task<TResult>>.
6113 // Call WhenAny(IEnumerable<Task>) for basic functionality
6114 Task<Task> intermediate = WhenAny((IEnumerable<Task>)tasks);
6116 // Return a continuation task with the correct result type
6117 return intermediate.ContinueWith(Task<TResult>.TaskWhenAnyCast.Value, default,
6118 TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default);
6120 #endregion
6122 internal static Task<TResult> CreateUnwrapPromise<TResult>(Task outerTask, bool lookForOce)
6124 Debug.Assert(outerTask != null);
6126 return new UnwrapPromise<TResult>(outerTask, lookForOce);
6129 internal virtual Delegate[]? GetDelegateContinuationsForDebugger()
6131 //Avoid an infinite loop by making sure the continuation object is not a reference to istelf.
6132 if (m_continuationObject != this)
6133 return GetDelegatesFromContinuationObject(m_continuationObject);
6134 else
6135 return null;
6138 private static Delegate[]? GetDelegatesFromContinuationObject(object? continuationObject)
6140 if (continuationObject != null)
6142 if (continuationObject is Action singleAction)
6144 return new Delegate[] { AsyncMethodBuilderCore.TryGetStateMachineForDebugger(singleAction) };
6147 if (continuationObject is TaskContinuation taskContinuation)
6149 return taskContinuation.GetDelegateContinuationsForDebugger();
6152 if (continuationObject is Task continuationTask)
6154 Debug.Assert(continuationTask.m_action == null);
6155 Delegate[]? delegates = continuationTask.GetDelegateContinuationsForDebugger();
6156 if (delegates != null)
6157 return delegates;
6160 //We need this ITaskCompletionAction after the Task because in the case of UnwrapPromise
6161 //the VS debugger is more interested in the continuation than the internal invoke()
6162 if (continuationObject is ITaskCompletionAction singleCompletionAction)
6164 return new Delegate[] { new Action<Task>(singleCompletionAction.Invoke) };
6167 if (continuationObject is List<object?> continuationList)
6169 List<Delegate> result = new List<Delegate>();
6170 foreach (object? obj in continuationList)
6172 Delegate[]? innerDelegates = GetDelegatesFromContinuationObject(obj);
6173 if (innerDelegates != null)
6175 foreach (Delegate del in innerDelegates)
6177 if (del != null)
6178 result.Add(del);
6183 return result.ToArray();
6187 return null;
6190 //Do not remove: VS debugger calls this API directly using func-eval to populate data in the tasks window
6191 private static Task? GetActiveTaskFromId(int taskId)
6193 Task? task = null;
6194 s_currentActiveTasks?.TryGetValue(taskId, out task);
6195 return task;
6199 internal sealed class CompletionActionInvoker : IThreadPoolWorkItem
6201 private readonly ITaskCompletionAction m_action;
6202 private readonly Task m_completingTask;
6204 internal CompletionActionInvoker(ITaskCompletionAction action, Task completingTask)
6206 m_action = action;
6207 m_completingTask = completingTask;
6210 void IThreadPoolWorkItem.Execute()
6212 m_action.Invoke(m_completingTask);
6216 // Proxy class for better debugging experience
6217 internal class SystemThreadingTasks_TaskDebugView
6219 private readonly Task m_task;
6221 public SystemThreadingTasks_TaskDebugView(Task task)
6223 m_task = task;
6226 public object? AsyncState => m_task.AsyncState;
6227 public TaskCreationOptions CreationOptions => m_task.CreationOptions;
6228 public Exception? Exception => m_task.Exception;
6229 public int Id => m_task.Id;
6230 public bool CancellationPending => (m_task.Status == TaskStatus.WaitingToRun) && m_task.CancellationToken.IsCancellationRequested;
6231 public TaskStatus Status => m_task.Status;
6234 /// <summary>
6235 /// Specifies flags that control optional behavior for the creation and execution of tasks.
6236 /// </summary>
6237 // NOTE: These options are a subset of TaskContinuationsOptions, thus before adding a flag check it is
6238 // not already in use.
6239 [Flags]
6240 public enum TaskCreationOptions
6242 /// <summary>
6243 /// Specifies that the default behavior should be used.
6244 /// </summary>
6245 None = 0x0,
6247 /// <summary>
6248 /// A hint to a <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> to schedule a
6249 /// task in as fair a manner as possible, meaning that tasks scheduled sooner will be more likely to
6250 /// be run sooner, and tasks scheduled later will be more likely to be run later.
6251 /// </summary>
6252 PreferFairness = 0x01,
6254 /// <summary>
6255 /// Specifies that a task will be a long-running, course-grained operation. It provides a hint to the
6256 /// <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> that oversubscription may be
6257 /// warranted.
6258 /// </summary>
6259 LongRunning = 0x02,
6261 /// <summary>
6262 /// Specifies that a task is attached to a parent in the task hierarchy.
6263 /// </summary>
6264 AttachedToParent = 0x04,
6266 /// <summary>
6267 /// Specifies that an InvalidOperationException will be thrown if an attempt is made to attach a child task to the created task.
6268 /// </summary>
6269 DenyChildAttach = 0x08,
6271 /// <summary>
6272 /// Prevents the ambient scheduler from being seen as the current scheduler in the created task. This means that operations
6273 /// like StartNew or ContinueWith that are performed in the created task will see TaskScheduler.Default as the current scheduler.
6274 /// </summary>
6275 HideScheduler = 0x10,
6277 // 0x20 is already being used in TaskContinuationOptions
6279 /// <summary>
6280 /// Forces continuations added to the current task to be executed asynchronously.
6281 /// This option has precedence over TaskContinuationOptions.ExecuteSynchronously
6282 /// </summary>
6283 RunContinuationsAsynchronously = 0x40
6287 /// <summary>
6288 /// Task creation flags which are only used internally.
6289 /// </summary>
6290 [Flags]
6291 internal enum InternalTaskOptions
6293 /// <summary> Specifies "No internal task options" </summary>
6294 None,
6296 /// <summary>Used to filter out internal vs. public task creation options.</summary>
6297 InternalOptionsMask = 0x0000FF00,
6299 ContinuationTask = 0x0200,
6300 PromiseTask = 0x0400,
6302 /// <summary>
6303 /// Store the presence of TaskContinuationOptions.LazyCancellation, since it does not directly
6304 /// translate into any TaskCreationOptions.
6305 /// </summary>
6306 LazyCancellation = 0x1000,
6308 /// <summary>Specifies that the task will be queued by the runtime before handing it over to the user.
6309 /// This flag will be used to skip the cancellationtoken registration step, which is only meant for unstarted tasks.</summary>
6310 QueuedByRuntime = 0x2000,
6312 /// <summary>
6313 /// Denotes that Dispose should be a complete nop for a Task. Used when constructing tasks that are meant to be cached/reused.
6314 /// </summary>
6315 DoNotDispose = 0x4000
6318 /// <summary>
6319 /// Specifies flags that control optional behavior for the creation and execution of continuation tasks.
6320 /// </summary>
6321 [Flags]
6322 public enum TaskContinuationOptions
6324 /// <summary>
6325 /// Default = "Continue on any, no task options, run asynchronously"
6326 /// Specifies that the default behavior should be used. Continuations, by default, will
6327 /// be scheduled when the antecedent task completes, regardless of the task's final <see
6328 /// cref="System.Threading.Tasks.TaskStatus">TaskStatus</see>.
6329 /// </summary>
6330 None = 0,
6332 // These are identical to their meanings and values in TaskCreationOptions
6334 /// <summary>
6335 /// A hint to a <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> to schedule a
6336 /// task in as fair a manner as possible, meaning that tasks scheduled sooner will be more likely to
6337 /// be run sooner, and tasks scheduled later will be more likely to be run later.
6338 /// </summary>
6339 PreferFairness = 0x01,
6341 /// <summary>
6342 /// Specifies that a task will be a long-running, course-grained operation. It provides
6343 /// a hint to the <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> that
6344 /// oversubscription may be warranted.
6345 /// </summary>
6346 LongRunning = 0x02,
6347 /// <summary>
6348 /// Specifies that a task is attached to a parent in the task hierarchy.
6349 /// </summary>
6350 AttachedToParent = 0x04,
6352 /// <summary>
6353 /// Specifies that an InvalidOperationException will be thrown if an attempt is made to attach a child task to the created task.
6354 /// </summary>
6355 DenyChildAttach = 0x08,
6356 /// <summary>
6357 /// Prevents the ambient scheduler from being seen as the current scheduler in the created task. This means that operations
6358 /// like StartNew or ContinueWith that are performed in the created task will see TaskScheduler.Default as the current scheduler.
6359 /// </summary>
6360 HideScheduler = 0x10,
6362 /// <summary>
6363 /// In the case of continuation cancellation, prevents completion of the continuation until the antecedent has completed.
6364 /// </summary>
6365 LazyCancellation = 0x20,
6367 RunContinuationsAsynchronously = 0x40,
6369 // These are specific to continuations
6371 /// <summary>
6372 /// Specifies that the continuation task should not be scheduled if its antecedent ran to completion.
6373 /// This option is not valid for multi-task continuations.
6374 /// </summary>
6375 NotOnRanToCompletion = 0x10000,
6376 /// <summary>
6377 /// Specifies that the continuation task should not be scheduled if its antecedent threw an unhandled
6378 /// exception. This option is not valid for multi-task continuations.
6379 /// </summary>
6380 NotOnFaulted = 0x20000,
6381 /// <summary>
6382 /// Specifies that the continuation task should not be scheduled if its antecedent was canceled. This
6383 /// option is not valid for multi-task continuations.
6384 /// </summary>
6385 NotOnCanceled = 0x40000,
6386 /// <summary>
6387 /// Specifies that the continuation task should be scheduled only if its antecedent ran to
6388 /// completion. This option is not valid for multi-task continuations.
6389 /// </summary>
6390 OnlyOnRanToCompletion = NotOnFaulted | NotOnCanceled,
6391 /// <summary>
6392 /// Specifies that the continuation task should be scheduled only if its antecedent threw an
6393 /// unhandled exception. This option is not valid for multi-task continuations.
6394 /// </summary>
6395 OnlyOnFaulted = NotOnRanToCompletion | NotOnCanceled,
6396 /// <summary>
6397 /// Specifies that the continuation task should be scheduled only if its antecedent was canceled.
6398 /// This option is not valid for multi-task continuations.
6399 /// </summary>
6400 OnlyOnCanceled = NotOnRanToCompletion | NotOnFaulted,
6401 /// <summary>
6402 /// Specifies that the continuation task should be executed synchronously. With this option
6403 /// specified, the continuation will be run on the same thread that causes the antecedent task to
6404 /// transition into its final state. If the antecedent is already complete when the continuation is
6405 /// created, the continuation will run on the thread creating the continuation. Only very
6406 /// short-running continuations should be executed synchronously.
6407 /// </summary>
6408 ExecuteSynchronously = 0x80000
6411 // Special internal struct that we use to signify that we are not interested in
6412 // a Task<VoidTaskResult>'s result.
6413 internal struct VoidTaskResult { }
6415 // Interface to which all completion actions must conform.
6416 // This interface allows us to combine functionality and reduce allocations.
6417 // For example, see Task.SetOnInvokeMres, and its use in Task.SpinThenBlockingWait().
6418 // This code:
6419 // ManualResetEvent mres = new ManualResetEventSlim(false, 0);
6420 // Action<Task> completionAction = delegate { mres.Set() ; };
6421 // AddCompletionAction(completionAction);
6422 // gets replaced with this:
6423 // SetOnInvokeMres mres = new SetOnInvokeMres();
6424 // AddCompletionAction(mres);
6425 // For additional examples of where this is used, see internal classes Task.SignalOnInvokeCDE,
6426 // Task.WhenAllPromise, Task.WhenAllPromise<T>, TaskFactory.CompleteOnCountdownPromise,
6427 // TaskFactory.CompleteOnCountdownPromise<T>, and TaskFactory.CompleteOnInvokePromise.
6428 internal interface ITaskCompletionAction
6430 /// <summary>Invoked to run the completion action.</summary>
6431 void Invoke(Task completingTask);
6433 /// <summary>
6434 /// Some completion actions are considered internal implementation details of tasks,
6435 /// using the continuation mechanism only for performance reasons. Such actions perform
6436 /// known quantities and types of work, and can be invoked safely as a continuation even
6437 /// if the system wants to prevent arbitrary continuations from running synchronously.
6438 /// This should only return false for a limited set of implementations where a small amount
6439 /// of work is guaranteed to be performed, e.g. setting a ManualResetEventSlim.
6440 /// </summary>
6441 bool InvokeMayRunArbitraryCode { get; }
6444 // This class encapsulates all "unwrap" logic, and also implements ITaskCompletionAction,
6445 // which minimizes the allocations needed for queuing it to its antecedent. This
6446 // logic is used by both the Unwrap extension methods and the unwrap-style Task.Run methods.
6447 internal sealed class UnwrapPromise<TResult> : Task<TResult>, ITaskCompletionAction
6449 // The possible states for our UnwrapPromise, used by Invoke() to determine which logic to execute
6450 private const byte STATE_WAITING_ON_OUTER_TASK = 0; // Invoke() means "process completed outer task"
6451 private const byte STATE_WAITING_ON_INNER_TASK = 1; // Invoke() means "process completed inner task"
6452 private const byte STATE_DONE = 2; // Invoke() means "something went wrong and we are hosed!"
6454 // Keep track of our state; initialized to STATE_WAITING_ON_OUTER_TASK in the constructor
6455 private byte _state;
6457 // "Should we check for OperationCanceledExceptions on the outer task and interpret them as proxy cancellation?"
6458 // Unwrap() sets this to false, Run() sets it to true.
6459 private readonly bool _lookForOce;
6461 public UnwrapPromise(Task outerTask, bool lookForOce)
6462 : base((object?)null, outerTask.CreationOptions & TaskCreationOptions.AttachedToParent)
6464 Debug.Assert(outerTask != null, "Expected non-null outerTask");
6465 _lookForOce = lookForOce;
6466 _state = STATE_WAITING_ON_OUTER_TASK;
6468 if (AsyncCausalityTracer.LoggingOn)
6469 AsyncCausalityTracer.TraceOperationCreation(this, "Task.Unwrap");
6471 if (s_asyncDebuggingEnabled)
6472 AddToActiveTasks(this);
6474 // Link ourselves to the outer task.
6475 // If the outer task has already completed, take the fast path
6476 // of immediately transferring its results or processing the inner task.
6477 if (outerTask.IsCompleted)
6479 ProcessCompletedOuterTask(outerTask);
6481 else // Otherwise, process its completion asynchronously.
6483 outerTask.AddCompletionAction(this);
6487 // For ITaskCompletionAction
6488 public void Invoke(Task completingTask)
6490 // If we're ok to inline, process the task. Otherwise, we're too deep on the stack, and
6491 // we shouldn't run the continuation chain here, so queue a work item to call back here
6492 // to Invoke asynchronously.
6493 if (RuntimeHelpers.TryEnsureSufficientExecutionStack())
6495 InvokeCore(completingTask);
6497 else
6499 InvokeCoreAsync(completingTask);
6503 /// <summary>
6504 /// Processes the completed task. InvokeCore could be called twice:
6505 /// once for the outer task, once for the inner task.
6506 /// </summary>
6507 /// <param name="completingTask">The completing outer or inner task.</param>
6508 private void InvokeCore(Task completingTask)
6510 switch (_state)
6512 case STATE_WAITING_ON_OUTER_TASK:
6513 ProcessCompletedOuterTask(completingTask);
6514 // We bump the state inside of ProcessCompletedOuterTask because it can also be called from the constructor.
6515 break;
6516 case STATE_WAITING_ON_INNER_TASK:
6517 bool result = TrySetFromTask(completingTask, lookForOce: false);
6518 _state = STATE_DONE; // bump the state
6519 Debug.Assert(result, "Expected TrySetFromTask from inner task to succeed");
6520 break;
6521 default:
6522 Debug.Fail("UnwrapPromise in illegal state");
6523 break;
6527 // Calls InvokeCore asynchronously.
6528 private void InvokeCoreAsync(Task completingTask)
6530 // Queue a call to Invoke. If we're so deep on the stack that we're at risk of overflowing,
6531 // there's a high liklihood this thread is going to be doing lots more work before
6532 // returning to the thread pool (at the very least unwinding through thousands of
6533 // stack frames). So we queue to the global queue.
6534 ThreadPool.UnsafeQueueUserWorkItem(state =>
6536 // InvokeCore(completingTask);
6537 var tuple = (Tuple<UnwrapPromise<TResult>, Task>)state!;
6538 tuple.Item1.InvokeCore(tuple.Item2);
6539 }, Tuple.Create<UnwrapPromise<TResult>, Task>(this, completingTask));
6542 /// <summary>Processes the outer task once it's completed.</summary>
6543 /// <param name="task">The now-completed outer task.</param>
6544 private void ProcessCompletedOuterTask(Task task)
6546 Debug.Assert(task != null && task.IsCompleted, "Expected non-null, completed outer task");
6547 Debug.Assert(_state == STATE_WAITING_ON_OUTER_TASK, "We're in the wrong state!");
6549 // Bump our state before proceeding any further
6550 _state = STATE_WAITING_ON_INNER_TASK;
6552 switch (task.Status)
6554 // If the outer task did not complete successfully, then record the
6555 // cancellation/fault information to tcs.Task.
6556 case TaskStatus.Canceled:
6557 case TaskStatus.Faulted:
6558 bool result = TrySetFromTask(task, _lookForOce);
6559 Debug.Assert(result, "Expected TrySetFromTask from outer task to succeed");
6560 break;
6562 // Otherwise, process the inner task it returned.
6563 case TaskStatus.RanToCompletion:
6564 ProcessInnerTask(task is Task<Task<TResult>> taskOfTaskOfTResult ? // it's either a Task<Task> or Task<Task<TResult>>
6565 taskOfTaskOfTResult.Result : ((Task<Task>)task).Result);
6566 break;
6570 /// <summary>Transfer the completion status from "task" to ourself.</summary>
6571 /// <param name="task">The source task whose results should be transfered to this.</param>
6572 /// <param name="lookForOce">Whether or not to look for OperationCanceledExceptions in task's exceptions if it faults.</param>
6573 /// <returns>true if the transfer was successful; otherwise, false.</returns>
6574 private bool TrySetFromTask(Task task, bool lookForOce)
6576 Debug.Assert(task != null && task.IsCompleted, "TrySetFromTask: Expected task to have completed.");
6578 if (AsyncCausalityTracer.LoggingOn)
6579 AsyncCausalityTracer.TraceOperationRelation(this, CausalityRelation.Join);
6581 bool result = false;
6582 switch (task.Status)
6584 case TaskStatus.Canceled:
6585 result = TrySetCanceled(task.CancellationToken, task.GetCancellationExceptionDispatchInfo());
6586 break;
6588 case TaskStatus.Faulted:
6589 ReadOnlyCollection<ExceptionDispatchInfo> edis = task.GetExceptionDispatchInfos();
6590 ExceptionDispatchInfo oceEdi;
6591 if (lookForOce && edis.Count > 0 &&
6592 (oceEdi = edis[0]) != null &&
6593 oceEdi.SourceException is OperationCanceledException oce)
6595 result = TrySetCanceled(oce.CancellationToken, oceEdi);
6597 else
6599 result = TrySetException(edis);
6601 break;
6603 case TaskStatus.RanToCompletion:
6604 if (AsyncCausalityTracer.LoggingOn)
6605 AsyncCausalityTracer.TraceOperationCompletion(this, AsyncCausalityStatus.Completed);
6607 if (Task.s_asyncDebuggingEnabled)
6608 RemoveFromActiveTasks(this);
6610 result = TrySetResult(task is Task<TResult> taskTResult ? taskTResult.Result : default);
6611 break;
6613 return result;
6616 /// <summary>
6617 /// Processes the inner task of a Task{Task} or Task{Task{TResult}},
6618 /// transferring the appropriate results to ourself.
6619 /// </summary>
6620 /// <param name="task">The inner task returned by the task provided by the user.</param>
6621 private void ProcessInnerTask(Task? task)
6623 // If the inner task is null, the proxy should be canceled.
6624 if (task == null)
6626 TrySetCanceled(default);
6627 _state = STATE_DONE; // ... and record that we are done
6630 // Fast path for if the inner task is already completed
6631 else if (task.IsCompleted)
6633 TrySetFromTask(task, lookForOce: false);
6634 _state = STATE_DONE; // ... and record that we are done
6637 // The inner task exists but is not yet complete, so when it does complete,
6638 // take some action to set our completion state.
6639 else
6641 task.AddCompletionAction(this);
6642 // We'll record that we are done when Invoke() is called.
6646 public bool InvokeMayRunArbitraryCode => true;