1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
5 // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
9 // 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
24 /// Represents the current stage in the lifecycle of a <see cref="Task"/>.
26 public enum TaskStatus
29 /// The task has been initialized but has not yet been scheduled.
33 /// The task is waiting to be activated and scheduled internally by the .NET Framework infrastructure.
37 /// The task has been scheduled for execution but has not yet begun executing.
41 /// The task is running but has not yet completed.
45 // /// The task is currently blocked in a wait state.
49 /// The task has finished executing and is implicitly waiting for
50 /// attached child tasks to complete.
52 WaitingForChildrenToComplete
,
54 /// The task completed execution successfully.
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.
64 /// The task completed due to an unhandled exception.
70 /// Represents an asynchronous operation.
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:
81 /// var t = Task.Factory.StartNew(() => DoAction());
84 /// Dim t = Task.Factory.StartNew(Function() DoAction())
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.
95 /// All members of <see cref="Task"/>, except for <see cref="Dispose()"/>, are thread-safe
96 /// and may be used from multiple threads concurrently.
99 /// For operations that return values, the <see cref="System.Threading.Tasks.Task{TResult}"/> class
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.
115 [DebuggerTypeProxy(typeof(SystemThreadingTasks_TaskDebugView
))]
116 [DebuggerDisplay("Id = {Id}, Status = {Status}, Method = {DebuggerDisplayMethodDescription}")]
117 public class Task
: IAsyncResult
, IDisposable
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
208 internal static void RemoveFromActiveTasks(Task task
)
210 if (s_currentActiveTasks
== null)
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.
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
;
255 /// Sets the internal completion event.
257 internal void SetCompleted()
259 ManualResetEventSlim
? mres
= m_completionEvent
;
260 if (mres
!= null) mres
.Set();
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.
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
;
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
,
303 m_stateFlags
= TASK_STATE_RAN_TO_COMPLETION
| optionFlags
;
306 /// <summary>Constructor for use with promise-style tasks that aren't configurable.</summary>
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
;
332 EnsureContingentPropertiesInitializedUnsafe().m_parent
= parent
;
336 TaskConstructorCore(null, state
, default, creationOptions
, InternalTaskOptions
.PromiseTask
, null);
340 /// Initializes a new <see cref="Task"/> with the specified action.
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)
350 /// Initializes a new <see cref="Task"/> with the specified action and <see cref="System.Threading.CancellationToken">CancellationToken</see>.
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.
359 public Task(Action action
, CancellationToken cancellationToken
)
360 : this(action
, null, null, cancellationToken
, TaskCreationOptions
.None
, InternalTaskOptions
.None
, null)
365 /// Initializes a new <see cref="Task"/> with the specified action and creation options.
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.
372 /// <exception cref="System.ArgumentNullException">
373 /// The <paramref name="action"/> argument is null.
375 /// <exception cref="System.ArgumentOutOfRangeException">
376 /// The <paramref name="creationOptions"/> argument specifies an invalid value for <see
377 /// cref="System.Threading.Tasks.TaskCreationOptions"/>.
379 public Task(Action action
, TaskCreationOptions creationOptions
)
380 : this(action
, null, Task
.InternalCurrentIfAttached(creationOptions
), default, creationOptions
, InternalTaskOptions
.None
, null)
385 /// Initializes a new <see cref="Task"/> with the specified action and creation options.
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.
393 /// <exception cref="System.ArgumentNullException">
394 /// The <paramref name="action"/> argument is null.
396 /// <exception cref="System.ArgumentOutOfRangeException">
397 /// The <paramref name="creationOptions"/> argument specifies an invalid value for <see
398 /// cref="System.Threading.Tasks.TaskCreationOptions"/>.
400 /// <exception cref="System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see>
401 /// has already been disposed.
403 public Task(Action action
, CancellationToken cancellationToken
, TaskCreationOptions creationOptions
)
404 : this(action
, null, Task
.InternalCurrentIfAttached(creationOptions
), cancellationToken
, creationOptions
, InternalTaskOptions
.None
, null)
410 /// Initializes a new <see cref="Task"/> with the specified action and state.
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.
417 public Task(Action
<object?> action
, object? state
)
418 : this(action
, state
, null, default, TaskCreationOptions
.None
, InternalTaskOptions
.None
, null)
423 /// Initializes a new <see cref="Task"/> with the specified action, state, and options.
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.
431 /// <exception cref="System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see>
432 /// has already been disposed.
434 public Task(Action
<object?> action
, object? state
, CancellationToken cancellationToken
)
435 : this(action
, state
, null, cancellationToken
, TaskCreationOptions
.None
, InternalTaskOptions
.None
, null)
440 /// Initializes a new <see cref="Task"/> with the specified action, state, and options.
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.
448 /// <exception cref="System.ArgumentNullException">
449 /// The <paramref name="action"/> argument is null.
451 /// <exception cref="System.ArgumentOutOfRangeException">
452 /// The <paramref name="creationOptions"/> argument specifies an invalid value for <see
453 /// cref="System.Threading.Tasks.TaskCreationOptions"/>.
455 public Task(Action
<object?> action
, object? state
, TaskCreationOptions creationOptions
)
456 : this(action
, state
, Task
.InternalCurrentIfAttached(creationOptions
), default, creationOptions
, InternalTaskOptions
.None
, null)
461 /// Initializes a new <see cref="Task"/> with the specified action, state, and options.
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.
470 /// <exception cref="System.ArgumentNullException">
471 /// The <paramref name="action"/> argument is null.
473 /// <exception cref="System.ArgumentOutOfRangeException">
474 /// The <paramref name="creationOptions"/> argument specifies an invalid value for <see
475 /// cref="System.Threading.Tasks.TaskCreationOptions"/>.
477 /// <exception cref="System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see>
478 /// has already been disposed.
480 public Task(Action
<object?> action
, object? state
, CancellationToken cancellationToken
, TaskCreationOptions creationOptions
)
481 : this(action
, state
, Task
.InternalCurrentIfAttached(creationOptions
), cancellationToken
, creationOptions
, InternalTaskOptions
.None
, null)
486 /// An internal constructor used by the factory methods on task and its descendent(s).
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
)
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();
517 /// Common logic used by the following internal ctors:
519 /// Task(object action, object state, Task parent, TaskCreationOptions options, TaskScheduler taskScheduler)
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
)
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
);
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");
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
:
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
;
573 Task
? parent
= props
.m_parent
;
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);
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.
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);
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);
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
);
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();
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
;
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
)
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
)
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.
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
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");
742 // Atomically clear the END_AWAIT_NOTIFICATION bit
743 int flags
= m_stateFlags
;
746 int oldFlags
= Interlocked
.CompareExchange(ref m_stateFlags
, flags
& (~TASK_STATE_WAIT_COMPLETION_NOTIFICATION
), flags
);
747 if (oldFlags
== flags
) break;
754 /// Calls the debugger notification method if the right bit is set and if
755 /// the task itself allows for the notification to proceed.
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();
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
)
778 task
.IsWaitNotificationEnabled
&&
779 task
.ShouldNotifyDebuggerOfWaitCompletion
) // potential recursion
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
;
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
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
);
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.
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
++;
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
);
900 /// Starts the <see cref="Task"/>, scheduling it for execution to the current <see
901 /// cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see>.
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.
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
914 Start(TaskScheduler
.Current
);
918 /// Starts the <see cref="Task"/>, scheduling it for execution to the specified <see
919 /// cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see>.
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.
925 /// <param name="scheduler">
926 /// The <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> with which to associate
927 /// and execute this task.
929 /// <exception cref="ArgumentNullException">
930 /// The <paramref name="scheduler"/> argument is null.
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
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);
974 /// Runs the <see cref="Task"/> synchronously on the current <see
975 /// cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see>.
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.
983 /// Tasks executed with <see cref="RunSynchronously()"/> will be associated with the current <see
984 /// cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see>.
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.
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
997 public void RunSynchronously()
999 InternalRunSynchronously(TaskScheduler
.Current
, waitForCompletion
: true);
1003 /// Runs the <see cref="Task"/> synchronously on the <see
1004 /// cref="System.Threading.Tasks.TaskScheduler">scheduler</see> provided.
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.
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.
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
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.
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);
1098 // we received an unexpected exception originating from a custom scheduler, which needs to be wrapped in a TSE and thrown
1101 // We had a problem with TryRunInline() or QueueTask().
1102 // Record the exception, marking ourselves as Completed/Faulted.
1103 TaskSchedulerException tse
= new TaskSchedulerException(e
);
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()
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);
1121 // We had a problem with waiting. Just re-throw.
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
);
1135 //// Helper methods for Factory StartNew methods.
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);
1159 /// Gets a unique ID for a <see cref="Task">Task</see> or task continuation instance.
1161 internal static 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
);
1173 if (TplEventSource
.Log
.IsEnabled())
1174 TplEventSource
.Log
.NewID(newId
);
1184 /// Gets a unique ID for this <see cref="Task">Task</see> instance.
1187 /// Task IDs are assigned on-demand and do not necessarily represent the order in the which Task
1188 /// instances were created.
1196 int newId
= NewId();
1197 Interlocked
.CompareExchange(ref m_taskId
, newId
, 0);
1205 /// Returns the unique ID of the currently executing <see cref="Task">Task</see>.
1207 public static int? CurrentId
1211 Task
? currentTask
= InternalCurrent
;
1212 if (currentTask
!= null)
1213 return currentTask
.Id
;
1220 /// Gets the <see cref="Task">Task</see> instance currently executing, or
1221 /// null if none exists.
1223 internal static Task
? InternalCurrent
=> t_currentTask
;
1226 /// Gets the Task instance currently executing if the specified creation options
1227 /// contain AttachedToParent.
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;
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.
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.
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");
1267 /// Gets the <see cref="System.Threading.Tasks.TaskStatus">TaskStatus</see> of this Task.
1269 public TaskStatus Status
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
;
1294 /// Gets whether this <see cref="Task">Task</see> instance has completed
1295 /// execution due to being canceled.
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>.
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
;
1309 /// Returns true if this task has a cancellation token and it was signaled.
1310 /// To be used internally in execute entry codepaths.
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
);
1325 /// Ensures that the contingent properties field has been initialized.
1326 /// ASSUMES THAT m_stateFlags IS ALREADY SET!
1328 /// <returns>The initialized contingent properties object.</returns>
1329 internal ContingentProperties
EnsureContingentPropertiesInitialized()
1331 return LazyInitializer
.EnsureInitialized(ref m_contingentProperties
, () => new ContingentProperties());
1335 /// Without synchronization, ensures that the contingent properties field has been initialized.
1336 /// ASSUMES THAT m_stateFlags IS ALREADY SET!
1338 /// <returns>The initialized contingent properties object.</returns>
1339 internal ContingentProperties
EnsureContingentPropertiesInitializedUnsafe()
1341 return m_contingentProperties
?? (m_contingentProperties
= new ContingentProperties());
1345 /// This internal property provides access to the CancellationToken that was set on the task
1346 /// when it was constructed.
1348 internal CancellationToken CancellationToken
1352 ContingentProperties
? props
= Volatile
.Read(ref m_contingentProperties
);
1353 return (props
== null) ? default : props
.m_cancellationToken
;
1358 /// Gets whether this <see cref="Task"/> threw an OperationCanceledException while its CancellationToken was signaled.
1360 internal bool IsCancellationAcknowledged
=> (m_stateFlags
& TASK_STATE_CANCELLATIONACKNOWLEDGED
) != 0;
1364 /// Gets whether this <see cref="Task">Task</see> has completed.
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>.
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
;
1391 /// Gets the <see cref="System.Threading.Tasks.TaskCreationOptions">TaskCreationOptions</see> used
1392 /// to create this task.
1394 public TaskCreationOptions CreationOptions
=> Options
& (TaskCreationOptions
)(~InternalTaskOptions
.InternalOptionsMask
);
1397 /// Gets a <see cref="System.Threading.WaitHandle"/> that can be used to wait for the task to
1401 /// Using the wait functionality provided by <see cref="Wait()"/>
1402 /// should be preferred over using <see cref="IAsyncResult.AsyncWaitHandle"/> for similar
1405 /// <exception cref="System.ObjectDisposedException">
1406 /// The <see cref="Task"/> has been disposed.
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;
1417 ThrowHelper
.ThrowObjectDisposedException(ExceptionResource
.Task_ThrowIfDisposed
);
1419 return CompletedEvent
.WaitHandle
;
1424 /// Gets the state object supplied when the <see cref="Task">Task</see> was created,
1425 /// or null if none was supplied.
1427 public object? AsyncState
=> m_stateObject
;
1430 /// Gets an indication of whether the asynchronous operation completed synchronously.
1432 /// <value>true if the asynchronous operation completed synchronously; otherwise, false.</value>
1433 bool IAsyncResult
.CompletedSynchronously
=> false;
1436 /// Provides access to the TaskScheduler responsible for executing this Task.
1438 internal TaskScheduler
? ExecutingTaskScheduler
=> m_taskScheduler
;
1441 /// Provides access to factory methods for creating <see cref="Task"/> and <see cref="Task{TResult}"/> instances.
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.
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);
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.
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.
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.
1479 return contingentProps
.m_completionEvent
;
1485 /// Whether an exception has been stored into the task.
1487 internal bool ExceptionRecorded
1491 ContingentProperties
? props
= Volatile
.Read(ref m_contingentProperties
);
1492 return (props
!= null) && (props
.m_exceptionsHolder
!= null) && (props
.m_exceptionsHolder
.ContainsFaultList
);
1497 /// Gets whether the <see cref="Task"/> completed due to an unhandled exception.
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.
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);
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
1513 internal ExecutionContext
? CapturedContext
1517 if ((m_stateFlags
& TASK_STATE_EXECUTIONCONTEXT_IS_NULL
) == TASK_STATE_EXECUTIONCONTEXT_IS_NULL
)
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
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
1546 /// Disposes the <see cref="Task"/>, releasing all of its unmanaged resources.
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>.
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>.
1561 public void Dispose()
1564 GC
.SuppressFinalize(this);
1568 /// Disposes the <see cref="Task"/>, releasing all of its unmanaged resources.
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()"/>.
1575 /// Unlike most of the members of <see cref="Task"/>, this method is not thread-safe.
1577 protected virtual void Dispose(bool 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)
1589 // Task must be completed to dispose
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
);
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
;
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
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
;
1634 /// Schedules the task for execution.
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
)
1651 // A cancel has snuck in before we could get started. Quietly exit.
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);
1678 // The scheduler had a problem queueing this task. Record the exception, leaving this task in
1680 TaskSchedulerException tse
= new TaskSchedulerException(e
);
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()
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.
1707 /// Adds an exception to the list of exceptions this task has thrown.
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);
1717 /// Adds an exception to the list of exceptions this task has thrown.
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");
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
>;
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
;
1738 !representsCancellation
||
1740 (eoAsEdi
!= null && eoAsEdi
.SourceException
is OperationCanceledException
),
1741 "representsCancellation should be true only if an OCE was provided.");
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);
1764 props
.m_exceptionsHolder
.Add(exceptionObject
, representsCancellation
);
1769 /// Returns a list of exceptions by aggregating the holder's contents. Or null if
1770 /// no exceptions have been thrown.
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
)
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
);
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
1859 /// Throws an aggregate exception if the task contains exceptions.
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();
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
);
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
));
1900 RuntimeExceptionHelpers
.ReportUnhandledException(edi
.SourceException
);
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
))
1909 // Propagate the exception(s) on the ThreadPool
1910 ThreadPool
.QueueUserWorkItem(state
=> ((ExceptionDispatchInfo
)state
!).Throw(), edi
);
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
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
;
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
1943 internal bool IsExceptionObservedByParent
=> (m_stateFlags
& TASK_STATE_EXCEPTIONOBSERVEDBYPARENT
) != 0;
1946 /// Checks whether the body was ever invoked. Used by task scheduler code to verify custom schedulers actually ran the task.
1948 internal bool IsDelegateInvoked
=> (m_stateFlags
& TASK_STATE_DELEGATE_INVOKED
) != 0;
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.
1961 internal void Finish(bool userDelegateExecute
)
1963 if (m_contingentProperties
== null)
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
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.
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
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.
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
);
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);
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
2084 cp
.UnregisterCancellationCallback();
2087 // ready to run continuations and notify parent.
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()
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.
2106 ContingentProperties
? cp
= m_contingentProperties
;
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
;
2126 && ((parent
.CreationOptions
& TaskCreationOptions
.DenyChildAttach
) == 0)
2127 && (((TaskCreationOptions
)(m_stateFlags
& OptionsMask
)) & TaskCreationOptions
.AttachedToParent
) != 0)
2129 parent
.ProcessChildCompletion(this);
2134 /// This is called by children of this task when they are completed.
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
;
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.
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
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;
2221 /// Outermost entry function to execute this task. Handles all aspects of executing a task on the caller thread.
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.
2238 if (!IsCancellationRequested
& !IsCanceled
)
2240 ExecuteWithThreadLocal(ref t_currentTask
);
2244 ExecuteEntryCancellationRequestedOrCanceled();
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.
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
);
2269 ExecuteEntryCancellationRequestedOrCanceled();
2273 internal void ExecuteEntryCancellationRequestedOrCanceled()
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();
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
);
2303 log
.TaskStarted(TaskScheduler
.Current
.Id
, 0, this.Id
);
2306 bool loggingOn
= AsyncCausalityTracer
.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
;
2321 // No context, just run the task directly.
2326 // Invoke it under the captured ExecutionContext
2327 if (threadPoolThread
is null)
2329 ExecutionContext
.RunInternal(ec
, s_ecCallback
, this);
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
);
2344 AsyncCausalityTracer
.TraceSynchronousWorkCompletion(CausalitySynchronousWork
.Execution
);
2350 currentTaskSlot
= previousTask
;
2352 // ETW event for Task Completed
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
);
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();
2375 /// The actual code which invokes the body of the task. This can be overridden in derived types.
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
)
2387 if (m_action
is Action
<object?> actionWithState
)
2389 actionWithState(m_stateObject
);
2392 Debug
.Fail("Invalid m_action in Task");
2396 /// Performs whatever handling is necessary for an unhandled exception. Normally
2397 /// this just entails adding the exception to the holder object.
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);
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.
2437 /// <returns>An object used to await this task.</returns>
2438 public ConfiguredTaskAwaitable
ConfigureAwait(bool continueOnCapturedContext
)
2440 return new ConfiguredTaskAwaitable(this, continueOnCapturedContext
);
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.
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.
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
);
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).
2505 if (!AddTaskContinuation(tc
, addBeforeOthers
: false))
2506 tc
.Run(this, canInlineContinuationTask
: false);
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);
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.
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.
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);
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);
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>
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.
2581 public static YieldAwaitable
Yield()
2583 return new YieldAwaitable();
2588 /// Waits for the <see cref="Task"/> to complete execution.
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"/>.
2599 Wait(Timeout
.Infinite
, default);
2602 Debug
.Assert(waitResult
, "expected wait to succeed");
2607 /// Waits for the <see cref="Task"/> to complete execution.
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.
2614 /// true if the <see cref="Task"/> completed execution within the allotted time; otherwise, false.
2616 /// <exception cref="System.AggregateException">
2617 /// The <see cref="Task"/> was canceled -or- an exception was thrown during the execution of the <see
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"/>.
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);
2638 /// Waits for the <see cref="Task"/> to complete execution.
2640 /// <param name="cancellationToken">
2641 /// A <see cref="CancellationToken"/> to observe while waiting for the task to complete.
2643 /// <exception cref="System.OperationCanceledException">
2644 /// The <paramref name="cancellationToken"/> was canceled.
2646 /// <exception cref="System.AggregateException">
2647 /// The <see cref="Task"/> was canceled -or- an exception was thrown during the execution of the <see
2650 public void Wait(CancellationToken cancellationToken
)
2652 Wait(Timeout
.Infinite
, cancellationToken
);
2657 /// Waits for the <see cref="Task"/> to complete execution.
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,
2665 /// <exception cref="System.ArgumentOutOfRangeException">
2666 /// <paramref name="millisecondsTimeout"/> is a negative number other than -1, which represents an
2667 /// infinite time-out.
2669 /// <exception cref="System.AggregateException">
2670 /// The <see cref="Task"/> was canceled -or- an exception was thrown during the execution of the <see
2673 public bool Wait(int millisecondsTimeout
)
2675 return Wait(millisecondsTimeout
, default);
2680 /// Waits for the <see cref="Task"/> to complete execution.
2682 /// <param name="millisecondsTimeout">
2683 /// The number of milliseconds to wait, or <see cref="System.Threading.Timeout.Infinite"/> (-1) to
2684 /// wait indefinitely.
2686 /// <param name="cancellationToken">
2687 /// A <see cref="CancellationToken"/> to observe while waiting for the task to complete.
2690 /// true if the <see cref="Task"/> completed execution within the allotted time; otherwise, false.
2692 /// <exception cref="System.AggregateException">
2693 /// The <see cref="Task"/> was canceled -or- an exception was thrown during the execution of the <see
2696 /// <exception cref="System.ArgumentOutOfRangeException">
2697 /// <paramref name="millisecondsTimeout"/> is a negative number other than -1, which represents an
2698 /// infinite time-out.
2700 /// <exception cref="System.OperationCanceledException">
2701 /// The <paramref name="cancellationToken"/> was canceled.
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)
2715 // Wait, and then return if we're still not done.
2716 if (!InternalWait(millisecondsTimeout
, cancellationToken
))
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.");
2741 // Convenience method that wraps any scheduler exception in a TaskSchedulerException
2743 private bool WrappedTryRunInline()
2745 if (m_taskScheduler
== null)
2750 return m_taskScheduler
.TryRunInline(this, true);
2754 throw new TaskSchedulerException(e
);
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.
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
;
2777 // ETW event for Task Wait Begin
2778 TplEventSource log
= TplEventSource
.Log
;
2779 bool etwIsEnabled
= log
.IsEnabled();
2782 Task
? currentTask
= Task
.InternalCurrent
;
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.
2805 returnValue
= SpinThenBlockingWait(millisecondsTimeout
, cancellationToken
);
2808 Debug
.Assert(IsCompleted
|| millisecondsTimeout
!= Timeout
.Infinite
);
2810 // ETW event for Task Wait End
2813 Task
? currentTask
= Task
.InternalCurrent
;
2814 if (currentTask
!= null)
2816 log
.TaskWaitEnd(currentTask
.m_taskScheduler
!.Id
, currentTask
.Id
, this.Id
);
2820 log
.TaskWaitEnd(TaskScheduler
.Default
.Id
, 0, this.Id
);
2822 // logically the continuation is empty so we immediately fire
2823 log
.TaskWaitContinuationComplete(this.Id
);
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);
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;
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.
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
);
2860 var mres
= new SetOnInvokeMres();
2863 AddCompletionAction(mres
, addBeforeOthers
: true);
2866 returnValue
= mres
.Wait(Timeout
.Infinite
, cancellationToken
);
2870 uint elapsedTimeTicks
= ((uint)Environment
.TickCount
) - startTimeTicks
;
2871 if (elapsedTimeTicks
< millisecondsTimeout
)
2873 returnValue
= mres
.Wait((int)(millisecondsTimeout
- elapsedTimeTicks
), cancellationToken
);
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.
2889 /// Spins briefly while checking IsCompleted
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.
2904 int spinCount
= Threading
.SpinWait
.SpinCountforSpinBeforeWait
;
2905 var spinner
= new SpinWait();
2906 while (spinner
.Count
< spinCount
)
2908 spinner
.SpinOnce(sleep1Threshold
: -1);
2920 /// Cancels the <see cref="Task"/>.
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.
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);
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.
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)
3006 CancellationCleanupLogic();
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)
3052 var oce
= cancellationException
as OperationCanceledException
;
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.");
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
);
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.
3099 /// Sets the task's cancellation acknowledged flag.
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
;
3123 NotifyParentIfPotentiallyAttachedTask();
3124 props
.SetCompleted();
3126 FinishContinuations();
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.
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
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");
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
3218 // Continuation passing functionality (aka ContinueWith)
3225 /// Runs all of the continuations, as appropriate.
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())
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();
3267 // Handle the single Action case.
3269 AwaitTaskContinuation
.RunOrScheduleAction(action
, canInlineContinuations
);
3270 LogFinishCompletionNotification();
3273 // Handle the single TaskContinuation case.
3274 case TaskContinuation tc
:
3275 tc
.Run(this, canInlineContinuations
);
3276 LogFinishCompletionNotification();
3279 // Handle the single ITaskCompletionAction case.
3280 case ITaskCompletionAction completionAction
:
3281 RunOrQueueCompletionAction(completionAction
, canInlineContinuations
);
3282 LogFinishCompletionNotification();
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.
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);
3340 AwaitTaskContinuation
.RunOrScheduleAction(action
, allowInlining
: false);
3344 Debug
.Assert(currentContinuation
is TaskContinuation
);
3345 ((TaskContinuation
)currentContinuation
).Run(this, canInlineContinuationTask
: false);
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)
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
);
3372 AwaitTaskContinuation
.RunOrScheduleAction(action
, canInlineContinuations
);
3375 case TaskContinuation tc
:
3376 tc
.Run(this, canInlineContinuations
);
3380 Debug
.Assert(currentContinuation
is ITaskCompletionAction
);
3381 RunOrQueueCompletionAction((ITaskCompletionAction
)currentContinuation
, canInlineContinuations
);
3386 LogFinishCompletionNotification();
3389 private void RunOrQueueCompletionAction(ITaskCompletionAction completionAction
, bool allowInlining
)
3391 if (allowInlining
|| !completionAction
.InvokeMayRunArbitraryCode
)
3393 completionAction
.Invoke(this);
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
3411 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
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.
3417 /// <returns>A new continuation <see cref="Task"/>.</returns>
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.
3423 /// <exception cref="System.ArgumentNullException">
3424 /// The <paramref name="continuationAction"/> argument is null.
3426 public Task
ContinueWith(Action
<Task
> continuationAction
)
3428 return ContinueWith(continuationAction
, TaskScheduler
.Current
, default, TaskContinuationOptions
.None
);
3432 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
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.
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>
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.
3445 /// <exception cref="System.ArgumentNullException">
3446 /// The <paramref name="continuationAction"/> argument is null.
3448 /// <exception cref="System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see>
3449 /// has already been disposed.
3451 public Task
ContinueWith(Action
<Task
> continuationAction
, CancellationToken cancellationToken
)
3453 return ContinueWith(continuationAction
, TaskScheduler
.Current
, cancellationToken
, TaskContinuationOptions
.None
);
3457 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
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.
3463 /// <param name="scheduler">
3464 /// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its execution.
3466 /// <returns>A new continuation <see cref="Task"/>.</returns>
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.
3472 /// <exception cref="System.ArgumentNullException">
3473 /// The <paramref name="continuationAction"/> argument is null.
3475 /// <exception cref="System.ArgumentNullException">
3476 /// The <paramref name="scheduler"/> argument is null.
3478 public Task
ContinueWith(Action
<Task
> continuationAction
, TaskScheduler scheduler
)
3480 return ContinueWith(continuationAction
, scheduler
, default, TaskContinuationOptions
.None
);
3484 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
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.
3490 /// <param name="continuationOptions">
3491 /// Options for when the continuation is scheduled and how it behaves. This includes criteria, such
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>.
3497 /// <returns>A new continuation <see cref="Task"/>.</returns>
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.
3504 /// <exception cref="System.ArgumentNullException">
3505 /// The <paramref name="continuationAction"/> argument is null.
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>.
3511 public Task
ContinueWith(Action
<Task
> continuationAction
, TaskContinuationOptions continuationOptions
)
3513 return ContinueWith(continuationAction
, TaskScheduler
.Current
, default, continuationOptions
);
3517 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
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.
3523 /// <param name="continuationOptions">
3524 /// Options for when the continuation is scheduled and how it behaves. This includes criteria, such
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>.
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
3535 /// <returns>A new continuation <see cref="Task"/>.</returns>
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.
3541 /// <exception cref="System.ArgumentNullException">
3542 /// The <paramref name="continuationAction"/> argument is null.
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>.
3548 /// <exception cref="System.ArgumentNullException">
3549 /// The <paramref name="scheduler"/> argument is null.
3551 /// <exception cref="System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see>
3552 /// has already been disposed.
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
;
3593 #region Action<Task, Object> continuation
3596 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
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.
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>
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.
3609 /// <exception cref="System.ArgumentNullException">
3610 /// The <paramref name="continuationAction"/> argument is null.
3612 public Task
ContinueWith(Action
<Task
, object?> continuationAction
, object? state
)
3614 return ContinueWith(continuationAction
, state
, TaskScheduler
.Current
, default, TaskContinuationOptions
.None
);
3618 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
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.
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>
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.
3632 /// <exception cref="System.ArgumentNullException">
3633 /// The <paramref name="continuationAction"/> argument is null.
3635 /// <exception cref="System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see>
3636 /// has already been disposed.
3638 public Task
ContinueWith(Action
<Task
, object?> continuationAction
, object? state
, CancellationToken cancellationToken
)
3640 return ContinueWith(continuationAction
, state
, TaskScheduler
.Current
, cancellationToken
, TaskContinuationOptions
.None
);
3644 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
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.
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.
3654 /// <returns>A new continuation <see cref="Task"/>.</returns>
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.
3660 /// <exception cref="System.ArgumentNullException">
3661 /// The <paramref name="continuationAction"/> argument is null.
3663 /// <exception cref="System.ArgumentNullException">
3664 /// The <paramref name="scheduler"/> argument is null.
3666 public Task
ContinueWith(Action
<Task
, object?> continuationAction
, object? state
, TaskScheduler scheduler
)
3668 return ContinueWith(continuationAction
, state
, scheduler
, default, TaskContinuationOptions
.None
);
3672 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
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.
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
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>.
3686 /// <returns>A new continuation <see cref="Task"/>.</returns>
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.
3693 /// <exception cref="System.ArgumentNullException">
3694 /// The <paramref name="continuationAction"/> argument is null.
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>.
3700 public Task
ContinueWith(Action
<Task
, object?> continuationAction
, object? state
, TaskContinuationOptions continuationOptions
)
3702 return ContinueWith(continuationAction
, state
, TaskScheduler
.Current
, default, continuationOptions
);
3706 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
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.
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
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>.
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
3725 /// <returns>A new continuation <see cref="Task"/>.</returns>
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.
3731 /// <exception cref="System.ArgumentNullException">
3732 /// The <paramref name="continuationAction"/> argument is null.
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>.
3738 /// <exception cref="System.ArgumentNullException">
3739 /// The <paramref name="scheduler"/> argument is null.
3741 /// <exception cref="System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see>
3742 /// has already been disposed.
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
;
3784 #region Func<Task, TResult> continuation
3787 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
3789 /// <typeparam name="TResult">
3790 /// The type of the result produced by the continuation.
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.
3796 /// <returns>A new continuation <see cref="Task{TResult}"/>.</returns>
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.
3802 /// <exception cref="System.ArgumentNullException">
3803 /// The <paramref name="continuationFunction"/> argument is null.
3805 public Task
<TResult
> ContinueWith
<TResult
>(Func
<Task
, TResult
> continuationFunction
)
3807 return ContinueWith
<TResult
>(continuationFunction
, TaskScheduler
.Current
, default,
3808 TaskContinuationOptions
.None
);
3813 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
3815 /// <typeparam name="TResult">
3816 /// The type of the result produced by the continuation.
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.
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>
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.
3829 /// <exception cref="System.ArgumentNullException">
3830 /// The <paramref name="continuationFunction"/> argument is null.
3832 /// <exception cref="System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see>
3833 /// has already been disposed.
3835 public Task
<TResult
> ContinueWith
<TResult
>(Func
<Task
, TResult
> continuationFunction
, CancellationToken cancellationToken
)
3837 return ContinueWith
<TResult
>(continuationFunction
, TaskScheduler
.Current
, cancellationToken
, TaskContinuationOptions
.None
);
3841 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
3843 /// <typeparam name="TResult">
3844 /// The type of the result produced by the continuation.
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.
3850 /// <param name="scheduler">
3851 /// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its execution.
3853 /// <returns>A new continuation <see cref="Task{TResult}"/>.</returns>
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.
3859 /// <exception cref="System.ArgumentNullException">
3860 /// The <paramref name="continuationFunction"/> argument is null.
3862 /// <exception cref="System.ArgumentNullException">
3863 /// The <paramref name="scheduler"/> argument is null.
3865 public Task
<TResult
> ContinueWith
<TResult
>(Func
<Task
, TResult
> continuationFunction
, TaskScheduler scheduler
)
3867 return ContinueWith
<TResult
>(continuationFunction
, scheduler
, default, TaskContinuationOptions
.None
);
3871 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
3873 /// <typeparam name="TResult">
3874 /// The type of the result produced by the continuation.
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.
3880 /// <param name="continuationOptions">
3881 /// Options for when the continuation is scheduled and how it behaves. This includes criteria, such
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>.
3887 /// <returns>A new continuation <see cref="Task{TResult}"/>.</returns>
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.
3894 /// <exception cref="System.ArgumentNullException">
3895 /// The <paramref name="continuationFunction"/> argument is null.
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>.
3901 public Task
<TResult
> ContinueWith
<TResult
>(Func
<Task
, TResult
> continuationFunction
, TaskContinuationOptions continuationOptions
)
3903 return ContinueWith
<TResult
>(continuationFunction
, TaskScheduler
.Current
, default, continuationOptions
);
3907 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
3909 /// <typeparam name="TResult">
3910 /// The type of the result produced by the continuation.
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.
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
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>.
3924 /// <param name="scheduler">
3925 /// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its
3928 /// <returns>A new continuation <see cref="Task{TResult}"/>.</returns>
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.
3934 /// <exception cref="System.ArgumentNullException">
3935 /// The <paramref name="continuationFunction"/> argument is null.
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>.
3941 /// <exception cref="System.ArgumentNullException">
3942 /// The <paramref name="scheduler"/> argument is null.
3944 /// <exception cref="System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see>
3945 /// has already been disposed.
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
;
3986 #region Func<Task, Object, TResult> continuation
3989 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
3991 /// <typeparam name="TResult">
3992 /// The type of the result produced by the continuation.
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.
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>
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.
4005 /// <exception cref="System.ArgumentNullException">
4006 /// The <paramref name="continuationFunction"/> argument is null.
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
);
4016 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
4018 /// <typeparam name="TResult">
4019 /// The type of the result produced by the continuation.
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.
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>
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.
4033 /// <exception cref="System.ArgumentNullException">
4034 /// The <paramref name="continuationFunction"/> argument is null.
4036 /// <exception cref="System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see>
4037 /// has already been disposed.
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
);
4045 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
4047 /// <typeparam name="TResult">
4048 /// The type of the result produced by the continuation.
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.
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.
4058 /// <returns>A new continuation <see cref="Task{TResult}"/>.</returns>
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.
4064 /// <exception cref="System.ArgumentNullException">
4065 /// The <paramref name="continuationFunction"/> argument is null.
4067 /// <exception cref="System.ArgumentNullException">
4068 /// The <paramref name="scheduler"/> argument is null.
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
);
4076 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
4078 /// <typeparam name="TResult">
4079 /// The type of the result produced by the continuation.
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.
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
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>.
4093 /// <returns>A new continuation <see cref="Task{TResult}"/>.</returns>
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.
4100 /// <exception cref="System.ArgumentNullException">
4101 /// The <paramref name="continuationFunction"/> argument is null.
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>.
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
);
4113 /// Creates a continuation that executes when the target <see cref="Task"/> completes.
4115 /// <typeparam name="TResult">
4116 /// The type of the result produced by the continuation.
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.
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
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>.
4131 /// <param name="scheduler">
4132 /// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its
4135 /// <returns>A new continuation <see cref="Task{TResult}"/>.</returns>
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.
4141 /// <exception cref="System.ArgumentNullException">
4142 /// The <paramref name="continuationFunction"/> argument is null.
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>.
4148 /// <exception cref="System.ArgumentNullException">
4149 /// The <paramref name="scheduler"/> argument is null.
4151 /// <exception cref="System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see>
4152 /// has already been disposed.
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
;
4194 /// Converts TaskContinuationOptions to TaskCreationOptions, and also does
4195 /// some validity checking along the way.
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
;
4251 /// Registers the continuation and possibly runs it (if the task is already finished).
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);
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);
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"
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.
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
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
)
4399 return true; // continuation successfully queued, so return true.
4404 // We didn't succeed in queuing the continuation, so 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
);
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?>;
4452 // Exchange was successful so we can skip the last comparison
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
);
4472 // null out that TaskContinuation entry, which will be interpreted as "to be cleaned up"
4473 continuationsLocalListRef
[index
] = null;
4484 /// Waits for all of the provided <see cref="Task"/> objects to complete execution.
4486 /// <param name="tasks">
4487 /// An array of <see cref="Task"/> instances on which to wait.
4489 /// <exception cref="System.ArgumentNullException">
4490 /// The <paramref name="tasks"/> argument is null.
4492 /// <exception cref="System.ArgumentNullException">
4493 /// The <paramref name="tasks"/> argument contains a null element.
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.
4499 [MethodImpl(MethodImplOptions
.NoOptimization
)] // this is needed for the parallel debugger
4500 public static void WaitAll(params Task
[] tasks
)
4505 WaitAllCore(tasks
, Timeout
.Infinite
, default);
4508 Debug
.Assert(waitResult
, "expected wait to succeed");
4513 /// Waits for all of the provided <see cref="Task"/> objects to complete execution.
4516 /// true if all of the <see cref="Task"/> instances completed execution within the allotted time;
4517 /// otherwise, false.
4519 /// <param name="tasks">
4520 /// An array of <see cref="Task"/> instances on which to wait.
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.
4526 /// <exception cref="System.ArgumentNullException">
4527 /// The <paramref name="tasks"/> argument is null.
4529 /// <exception cref="System.ArgumentException">
4530 /// The <paramref name="tasks"/> argument contains a null element.
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.
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"/>.
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);
4554 /// Waits for all of the provided <see cref="Task"/> objects to complete execution.
4557 /// true if all of the <see cref="Task"/> instances completed execution within the allotted time;
4558 /// otherwise, false.
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.
4565 /// <exception cref="System.ArgumentNullException">
4566 /// The <paramref name="tasks"/> argument is null.
4568 /// <exception cref="System.ArgumentException">
4569 /// The <paramref name="tasks"/> argument contains a null element.
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.
4575 /// <exception cref="System.ArgumentOutOfRangeException">
4576 /// <paramref name="millisecondsTimeout"/> is a negative number other than -1, which represents an
4577 /// infinite time-out.
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);
4586 /// Waits for all of the provided <see cref="Task"/> objects to complete execution.
4588 /// <param name="tasks">
4589 /// An array of <see cref="Task"/> instances on which to wait.
4591 /// <param name="cancellationToken">
4592 /// A <see cref="CancellationToken"/> to observe while waiting for the tasks to complete.
4594 /// <exception cref="System.ArgumentNullException">
4595 /// The <paramref name="tasks"/> argument is null.
4597 /// <exception cref="System.ArgumentException">
4598 /// The <paramref name="tasks"/> argument contains a null element.
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.
4604 /// <exception cref="System.OperationCanceledException">
4605 /// The <paramref name="cancellationToken"/> was canceled.
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
);
4614 /// Waits for all of the provided <see cref="Task"/> objects to complete execution.
4617 /// true if all of the <see cref="Task"/> instances completed execution within the allotted time;
4618 /// otherwise, false.
4620 /// <param name="tasks">
4621 /// An array of <see cref="Task"/> instances on which to wait.
4623 /// <param name="millisecondsTimeout">
4624 /// The number of milliseconds to wait, or <see cref="System.Threading.Timeout.Infinite"/> (-1) to
4625 /// wait indefinitely.
4627 /// <param name="cancellationToken">
4628 /// A <see cref="CancellationToken"/> to observe while waiting for the tasks to complete.
4630 /// <exception cref="System.ArgumentNullException">
4631 /// The <paramref name="tasks"/> argument is null.
4633 /// <exception cref="System.ArgumentException">
4634 /// The <paramref name="tasks"/> argument contains a null element.
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.
4640 /// <exception cref="System.ArgumentOutOfRangeException">
4641 /// <paramref name="millisecondsTimeout"/> is a negative number other than -1, which represents an
4642 /// infinite time-out.
4644 /// <exception cref="System.OperationCanceledException">
4645 /// The <paramref name="cancellationToken"/> was canceled.
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
)
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
];
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
);
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.
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
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
);
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
);
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
);
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);
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
4838 internal SetOnCountdownMres(int count
)
4840 Debug
.Assert(count
> 0, "Expected count > 0");
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;
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).
4858 internal static void AddExceptionsForCompletedTask(ref List
<Exception
>? exceptions
, Task t
)
4860 AggregateException
? ex
= t
.GetExceptions(true);
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
);
4879 /// Waits for any of the provided <see cref="Task"/> objects to complete execution.
4881 /// <param name="tasks">
4882 /// An array of <see cref="Task"/> instances on which to wait.
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.
4888 /// <exception cref="System.ArgumentException">
4889 /// The <paramref name="tasks"/> argument contains a null element.
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");
4900 /// Waits for any of the provided <see cref="Task"/> objects to complete execution.
4902 /// <param name="tasks">
4903 /// An array of <see cref="Task"/> instances on which to wait.
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.
4910 /// The index of the completed task in the <paramref name="tasks"/> array argument, or -1 if the
4911 /// timeout occurred.
4913 /// <exception cref="System.ArgumentNullException">
4914 /// The <paramref name="tasks"/> argument is null.
4916 /// <exception cref="System.ArgumentException">
4917 /// The <paramref name="tasks"/> argument contains a null element.
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"/>.
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);
4937 /// Waits for any of the provided <see cref="Task"/> objects to complete execution.
4939 /// <param name="tasks">
4940 /// An array of <see cref="Task"/> instances on which to wait.
4942 /// <param name="cancellationToken">
4943 /// A <see cref="CancellationToken"/> to observe while waiting for a task to complete.
4946 /// The index of the completed task in the <paramref name="tasks"/> array argument.
4948 /// <exception cref="System.ArgumentNullException">
4949 /// The <paramref name="tasks"/> argument is null.
4951 /// <exception cref="System.ArgumentException">
4952 /// The <paramref name="tasks"/> argument contains a null element.
4954 /// <exception cref="System.OperationCanceledException">
4955 /// The <paramref name="cancellationToken"/> was canceled.
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
);
4964 /// Waits for any of the provided <see cref="Task"/> objects to complete execution.
4966 /// <param name="tasks">
4967 /// An array of <see cref="Task"/> instances on which to wait.
4969 /// <param name="millisecondsTimeout">
4970 /// The number of milliseconds to wait, or <see cref="System.Threading.Timeout.Infinite"/> (-1) to
4971 /// wait indefinitely.
4974 /// The index of the completed task in the <paramref name="tasks"/> array argument, or -1 if the
4975 /// timeout occurred.
4977 /// <exception cref="System.ArgumentNullException">
4978 /// The <paramref name="tasks"/> argument is null.
4980 /// <exception cref="System.ArgumentException">
4981 /// The <paramref name="tasks"/> argument contains a null element.
4983 /// <exception cref="System.ArgumentOutOfRangeException">
4984 /// <paramref name="millisecondsTimeout"/> is a negative number other than -1, which represents an
4985 /// infinite time-out.
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);
4994 /// Waits for any of the provided <see cref="Task"/> objects to complete execution.
4996 /// <param name="tasks">
4997 /// An array of <see cref="Task"/> instances on which to wait.
4999 /// <param name="millisecondsTimeout">
5000 /// The number of milliseconds to wait, or <see cref="System.Threading.Timeout.Infinite"/> (-1) to
5001 /// wait indefinitely.
5003 /// <param name="cancellationToken">
5004 /// A <see cref="CancellationToken"/> to observe while waiting for a task to complete.
5007 /// The index of the completed task in the <paramref name="tasks"/> array argument, or -1 if the
5008 /// timeout occurred.
5010 /// <exception cref="System.ArgumentNullException">
5011 /// The <paramref name="tasks"/> argument is null.
5013 /// <exception cref="System.ArgumentException">
5014 /// The <paramref name="tasks"/> argument contains a null element.
5016 /// <exception cref="System.ArgumentOutOfRangeException">
5017 /// <paramref name="millisecondsTimeout"/> is a negative number other than -1, which represents an
5018 /// infinite time-out.
5020 /// <exception cref="System.OperationCanceledException">
5021 /// The <paramref name="cancellationToken"/> was canceled.
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
)
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
];
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
);
5070 Debug
.Assert(firstCompleted
.Status
== TaskStatus
.RanToCompletion
);
5071 signaledTaskIndex
= Array
.IndexOf(tasks
, firstCompleted
.Result
);
5072 Debug
.Assert(signaledTaskIndex
>= 0);
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
);
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.");
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.");
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.");
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.");
5181 /// Queues the specified work to run on the ThreadPool and returns a Task handle for that work.
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.
5188 public static Task
Run(Action action
)
5190 return Task
.InternalStartNew(null, action
, null, default, TaskScheduler
.Default
,
5191 TaskCreationOptions
.DenyChildAttach
, InternalTaskOptions
.None
);
5195 /// Queues the specified work to run on the ThreadPool and returns a Task handle for that work.
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.
5203 /// <exception cref="System.ObjectDisposedException">
5204 /// The CancellationTokenSource associated with <paramref name="cancellationToken"/> was disposed.
5206 public static Task
Run(Action action
, CancellationToken cancellationToken
)
5208 return Task
.InternalStartNew(null, action
, null, cancellationToken
, TaskScheduler
.Default
,
5209 TaskCreationOptions
.DenyChildAttach
, InternalTaskOptions
.None
);
5213 /// Queues the specified work to run on the ThreadPool and returns a Task(TResult) handle for that work.
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.
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
);
5227 /// Queues the specified work to run on the ThreadPool and returns a Task(TResult) handle for that work.
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.
5235 /// <exception cref="System.ObjectDisposedException">
5236 /// The CancellationTokenSource associated with <paramref name="cancellationToken"/> was disposed.
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
);
5245 /// Queues the specified work to run on the ThreadPool and returns a proxy for the
5246 /// Task returned by <paramref name="function"/>.
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.
5253 public static Task
Run(Func
<Task
?> function
)
5255 return Run(function
, default);
5260 /// Queues the specified work to run on the ThreadPool and returns a proxy for the
5261 /// Task returned by <paramref name="function"/>.
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.
5269 /// <exception cref="System.ObjectDisposedException">
5270 /// The CancellationTokenSource associated with <paramref name="cancellationToken"/> was disposed.
5272 public static Task
Run(Func
<Task
?> function
, CancellationToken cancellationToken
)
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);
5292 /// Queues the specified work to run on the ThreadPool and returns a proxy for the
5293 /// Task(TResult) returned by <paramref name="function"/>.
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.
5301 public static Task
<TResult
> Run
<TResult
>(Func
<Task
<TResult
>?> function
)
5303 return Run(function
, default);
5307 /// Queues the specified work to run on the ThreadPool and returns a proxy for the
5308 /// Task(TResult) returned by <paramref name="function"/>.
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.
5317 public static Task
<TResult
> Run
<TResult
>(Func
<Task
<TResult
>?> function
, CancellationToken cancellationToken
)
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);
5339 #region Delay methods
5342 /// Creates a Task that will complete after a time delay.
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.
5350 /// After the specified time delay, the Task is completed in RanToCompletion state.
5352 public static Task
Delay(TimeSpan delay
)
5354 return Delay(delay
, default);
5358 /// Creates a Task that will complete after a time delay.
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.
5366 /// <exception cref="System.ObjectDisposedException">
5367 /// The provided <paramref name="cancellationToken"/> has already been disposed.
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.
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
);
5386 /// Creates a Task that will complete after a time delay.
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.
5394 /// After the specified time delay, the Task is completed in RanToCompletion state.
5396 public static Task
Delay(int millisecondsDelay
)
5398 return Delay(millisecondsDelay
, default);
5402 /// Creates a Task that will complete after a time delay.
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.
5410 /// <exception cref="System.ObjectDisposedException">
5411 /// The provided <paramref name="cancellationToken"/> has already been disposed.
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.
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
);
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);
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.
5461 private void CompleteTimedOut()
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
);
5489 _registration
= token
.UnsafeRegister(state
=> ((DelayPromiseWithCancellation
)state
!).CompleteCanceled(), this);
5492 private void CompleteCanceled()
5494 if (TrySetCanceled(_token
))
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();
5512 /// Creates a task that will complete when all of the supplied tasks have completed.
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>
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.
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.
5525 /// If none of the tasks faulted and none of the tasks were canceled, the resulting task will end in the RanToCompletion state.
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.
5532 /// <exception cref="System.ArgumentNullException">
5533 /// The <paramref name="tasks"/> argument was null.
5535 /// <exception cref="System.ArgumentException">
5536 /// The <paramref name="tasks"/> collection contained a null task.
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
)
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
);
5568 // Delegate the rest to InternalWhenAll()
5569 return InternalWhenAll(taskList
.ToArray());
5573 /// Creates a task that will complete when all of the supplied tasks have completed.
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>
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.
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.
5586 /// If none of the tasks faulted and none of the tasks were canceled, the resulting task will end in the RanToCompletion state.
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.
5593 /// <exception cref="System.ArgumentNullException">
5594 /// The <paramref name="tasks"/> argument was null.
5596 /// <exception cref="System.ArgumentException">
5597 /// The <paramref name="tasks"/> array contained a null task.
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);
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
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.
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);
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");
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());
5723 if (AsyncCausalityTracer
.LoggingOn
)
5724 AsyncCausalityTracer
.TraceOperationCompletion(this, AsyncCausalityStatus
.Completed
);
5726 if (s_asyncDebuggingEnabled
)
5727 RemoveFromActiveTasks(this);
5732 Debug
.Assert(m_count
>= 0, "Count should never go below 0");
5735 public bool InvokeMayRunArbitraryCode
=> true;
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.
5741 internal override bool ShouldNotifyDebuggerOfWaitCompletion
=>
5742 base.ShouldNotifyDebuggerOfWaitCompletion
&&
5743 AnyTaskRequiresNotifyDebuggerOfWaitCompletion(m_tasks
);
5747 /// Creates a task that will complete when all of the supplied tasks have completed.
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>
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.
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.
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).
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.
5770 /// <exception cref="System.ArgumentNullException">
5771 /// The <paramref name="tasks"/> argument was null.
5773 /// <exception cref="System.ArgumentException">
5774 /// The <paramref name="tasks"/> collection contained a null task.
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
)
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
);
5806 // Delegate the rest to InternalWhenAll<TResult>().
5807 return InternalWhenAll
<TResult
>(taskList
.ToArray());
5811 /// Creates a task that will complete when all of the supplied tasks have completed.
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>
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.
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.
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).
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.
5834 /// <exception cref="System.ArgumentNullException">
5835 /// The <paramref name="tasks"/> argument was null.
5837 /// <exception cref="System.ArgumentException">
5838 /// The <paramref name="tasks"/> array contained a null task.
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
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.
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
) :
5887 Debug
.Assert(tasks
!= null, "Expected a non-null task array");
5888 Debug
.Assert(tasks
.Length
> 0, "Expected a non-zero length task array");
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");
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
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());
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;
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.
5981 internal override bool ShouldNotifyDebuggerOfWaitCompletion
=>
5982 base.ShouldNotifyDebuggerOfWaitCompletion
&&
5983 AnyTaskRequiresNotifyDebuggerOfWaitCompletion(m_tasks
);
5989 /// Creates a task that will complete when any of the supplied tasks have completed.
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>
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.
5997 /// <exception cref="System.ArgumentNullException">
5998 /// The <paramref name="tasks"/> argument was null.
6000 /// <exception cref="System.ArgumentException">
6001 /// The <paramref name="tasks"/> array contained a null task, or was empty.
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
);
6027 /// Creates a task that will complete when any of the supplied tasks have completed.
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>
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.
6035 /// <exception cref="System.ArgumentNullException">
6036 /// The <paramref name="tasks"/> argument was null.
6038 /// <exception cref="System.ArgumentException">
6039 /// The <paramref name="tasks"/> collection contained a null task, or was empty.
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
);
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
);
6064 /// Creates a task that will complete when any of the supplied tasks have completed.
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>
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.
6072 /// <exception cref="System.ArgumentNullException">
6073 /// The <paramref name="tasks"/> argument was null.
6075 /// <exception cref="System.ArgumentException">
6076 /// The <paramref name="tasks"/> array contained a null task, or was empty.
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
);
6093 /// Creates a task that will complete when any of the supplied tasks have completed.
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>
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.
6101 /// <exception cref="System.ArgumentNullException">
6102 /// The <paramref name="tasks"/> argument was null.
6104 /// <exception cref="System.ArgumentException">
6105 /// The <paramref name="tasks"/> collection contained a null task, or was empty.
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
);
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
);
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)
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
)
6183 return result
.ToArray();
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
)
6194 s_currentActiveTasks
?.TryGetValue(taskId
, out 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
)
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
)
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
;
6235 /// Specifies flags that control optional behavior for the creation and execution of tasks.
6237 // NOTE: These options are a subset of TaskContinuationsOptions, thus before adding a flag check it is
6238 // not already in use.
6240 public enum TaskCreationOptions
6243 /// Specifies that the default behavior should be used.
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.
6252 PreferFairness
= 0x01,
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
6262 /// Specifies that a task is attached to a parent in the task hierarchy.
6264 AttachedToParent
= 0x04,
6267 /// Specifies that an InvalidOperationException will be thrown if an attempt is made to attach a child task to the created task.
6269 DenyChildAttach
= 0x08,
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.
6275 HideScheduler
= 0x10,
6277 // 0x20 is already being used in TaskContinuationOptions
6280 /// Forces continuations added to the current task to be executed asynchronously.
6281 /// This option has precedence over TaskContinuationOptions.ExecuteSynchronously
6283 RunContinuationsAsynchronously
= 0x40
6288 /// Task creation flags which are only used internally.
6291 internal enum InternalTaskOptions
6293 /// <summary> Specifies "No internal task options" </summary>
6296 /// <summary>Used to filter out internal vs. public task creation options.</summary>
6297 InternalOptionsMask
= 0x0000FF00,
6299 ContinuationTask
= 0x0200,
6300 PromiseTask
= 0x0400,
6303 /// Store the presence of TaskContinuationOptions.LazyCancellation, since it does not directly
6304 /// translate into any TaskCreationOptions.
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,
6313 /// Denotes that Dispose should be a complete nop for a Task. Used when constructing tasks that are meant to be cached/reused.
6315 DoNotDispose
= 0x4000
6319 /// Specifies flags that control optional behavior for the creation and execution of continuation tasks.
6322 public enum TaskContinuationOptions
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>.
6332 // These are identical to their meanings and values in TaskCreationOptions
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.
6339 PreferFairness
= 0x01,
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.
6348 /// Specifies that a task is attached to a parent in the task hierarchy.
6350 AttachedToParent
= 0x04,
6353 /// Specifies that an InvalidOperationException will be thrown if an attempt is made to attach a child task to the created task.
6355 DenyChildAttach
= 0x08,
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.
6360 HideScheduler
= 0x10,
6363 /// In the case of continuation cancellation, prevents completion of the continuation until the antecedent has completed.
6365 LazyCancellation
= 0x20,
6367 RunContinuationsAsynchronously
= 0x40,
6369 // These are specific to continuations
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.
6375 NotOnRanToCompletion
= 0x10000,
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.
6380 NotOnFaulted
= 0x20000,
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.
6385 NotOnCanceled
= 0x40000,
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.
6390 OnlyOnRanToCompletion
= NotOnFaulted
| NotOnCanceled
,
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.
6395 OnlyOnFaulted
= NotOnRanToCompletion
| NotOnCanceled
,
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.
6400 OnlyOnCanceled
= NotOnRanToCompletion
| NotOnFaulted
,
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.
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().
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
);
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.
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
);
6499 InvokeCoreAsync(completingTask
);
6504 /// Processes the completed task. InvokeCore could be called twice:
6505 /// once for the outer task, once for the inner task.
6507 /// <param name="completingTask">The completing outer or inner task.</param>
6508 private void InvokeCore(Task completingTask
)
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.
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");
6522 Debug
.Fail("UnwrapPromise in illegal state");
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");
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
);
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());
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
);
6599 result
= TrySetException(edis
);
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);
6617 /// Processes the inner task of a Task{Task} or Task{Task{TResult}},
6618 /// transferring the appropriate results to ourself.
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.
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.
6641 task
.AddCompletionAction(this);
6642 // We'll record that we are done when Invoke() is called.
6646 public bool InvokeMayRunArbitraryCode
=> true;