summaryrefslogtreecommitdiff
path: root/src/mscorlib/src/System/Threading/Tasks/Task.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/mscorlib/src/System/Threading/Tasks/Task.cs')
-rw-r--r--src/mscorlib/src/System/Threading/Tasks/Task.cs7344
1 files changed, 7344 insertions, 0 deletions
diff --git a/src/mscorlib/src/System/Threading/Tasks/Task.cs b/src/mscorlib/src/System/Threading/Tasks/Task.cs
new file mode 100644
index 0000000000..36f8401a4d
--- /dev/null
+++ b/src/mscorlib/src/System/Threading/Tasks/Task.cs
@@ -0,0 +1,7344 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+//
+//
+// A schedulable unit of work.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Runtime;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.ExceptionServices;
+using System.Security;
+using System.Security.Permissions;
+using System.Threading;
+using System.Diagnostics;
+using System.Diagnostics.Contracts;
+using Microsoft.Win32;
+using System.Diagnostics.Tracing;
+
+// Disable the "reference to volatile field not treated as volatile" error.
+#pragma warning disable 0420
+
+namespace System.Threading.Tasks
+{
+
+ /// <summary>
+ /// Utility class for allocating structs as heap variables
+ /// </summary>
+ internal class Shared<T>
+ {
+ internal T Value;
+
+ internal Shared(T value)
+ {
+ this.Value = value;
+ }
+
+ }
+
+ /// <summary>
+ /// Represents the current stage in the lifecycle of a <see cref="Task"/>.
+ /// </summary>
+ public enum TaskStatus
+ {
+ /// <summary>
+ /// The task has been initialized but has not yet been scheduled.
+ /// </summary>
+ Created,
+ /// <summary>
+ /// The task is waiting to be activated and scheduled internally by the .NET Framework infrastructure.
+ /// </summary>
+ WaitingForActivation,
+ /// <summary>
+ /// The task has been scheduled for execution but has not yet begun executing.
+ /// </summary>
+ WaitingToRun,
+ /// <summary>
+ /// The task is running but has not yet completed.
+ /// </summary>
+ Running,
+ // /// <summary>
+ // /// The task is currently blocked in a wait state.
+ // /// </summary>
+ // Blocked,
+ /// <summary>
+ /// The task has finished executing and is implicitly waiting for
+ /// attached child tasks to complete.
+ /// </summary>
+ WaitingForChildrenToComplete,
+ /// <summary>
+ /// The task completed execution successfully.
+ /// </summary>
+ RanToCompletion,
+ /// <summary>
+ /// The task acknowledged cancellation by throwing an OperationCanceledException with its own CancellationToken
+ /// while the token was in signaled state, or the task's CancellationToken was already signaled before the
+ /// task started executing.
+ /// </summary>
+ Canceled,
+ /// <summary>
+ /// The task completed due to an unhandled exception.
+ /// </summary>
+ Faulted
+ }
+
+ /// <summary>
+ /// Represents an asynchronous operation.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// <see cref="Task"/> instances may be created in a variety of ways. The most common approach is by
+ /// using the Task type's <see cref="Factory"/> property to retrieve a <see
+ /// cref="System.Threading.Tasks.TaskFactory"/> instance that can be used to create tasks for several
+ /// purposes. For example, to create a <see cref="Task"/> that runs an action, the factory's StartNew
+ /// method may be used:
+ /// <code>
+ /// // C#
+ /// var t = Task.Factory.StartNew(() => DoAction());
+ ///
+ /// ' Visual Basic
+ /// Dim t = Task.Factory.StartNew(Function() DoAction())
+ /// </code>
+ /// </para>
+ /// <para>
+ /// The <see cref="Task"/> class also provides constructors that initialize the Task but that do not
+ /// schedule it for execution. For performance reasons, TaskFactory's StartNew method should be the
+ /// preferred mechanism for creating and scheduling computational tasks, but for scenarios where creation
+ /// and scheduling must be separated, the constructors may be used, and the task's <see cref="Start()"/>
+ /// method may then be used to schedule the task for execution at a later time.
+ /// </para>
+ /// <para>
+ /// All members of <see cref="Task"/>, except for <see cref="Dispose()"/>, are thread-safe
+ /// and may be used from multiple threads concurrently.
+ /// </para>
+ /// <para>
+ /// For operations that return values, the <see cref="System.Threading.Tasks.Task{TResult}"/> class
+ /// should be used.
+ /// </para>
+ /// <para>
+ /// For developers implementing custom debuggers, several internal and private members of Task may be
+ /// useful (these may change from release to release). The Int32 m_taskId field serves as the backing
+ /// store for the <see cref="Id"/> property, however accessing this field directly from a debugger may be
+ /// more efficient than accessing the same value through the property's getter method (the
+ /// s_taskIdCounter Int32 counter is used to retrieve the next available ID for a Task). Similarly, the
+ /// Int32 m_stateFlags field stores information about the current lifecycle stage of the Task,
+ /// information also accessible through the <see cref="Status"/> property. The m_action System.Object
+ /// field stores a reference to the Task's delegate, and the m_stateObject System.Object field stores the
+ /// async state passed to the Task by the developer. Finally, for debuggers that parse stack frames, the
+ /// InternalWait method serves a potential marker for when a Task is entering a wait operation.
+ /// </para>
+ /// </remarks>
+ [HostProtection(Synchronization = true, ExternalThreading = true)]
+ [DebuggerTypeProxy(typeof(SystemThreadingTasks_TaskDebugView))]
+ [DebuggerDisplay("Id = {Id}, Status = {Status}, Method = {DebuggerDisplayMethodDescription}")]
+ public class Task : IThreadPoolWorkItem, IAsyncResult, IDisposable
+ {
+ [ThreadStatic]
+ internal static Task t_currentTask; // The currently executing task.
+ [ThreadStatic]
+ private static StackGuard t_stackGuard; // The stack guard object for this thread
+
+ internal static int s_taskIdCounter; //static counter used to generate unique task IDs
+ private readonly static TaskFactory s_factory = new TaskFactory();
+
+ private volatile int m_taskId; // this task's unique ID. initialized only if it is ever requested
+
+ internal object m_action; // The body of the task. Might be Action<object>, Action<TState> or Action. Or possibly a Func.
+ // If m_action is set to null it will indicate that we operate in the
+ // "externally triggered completion" mode, which is exclusively meant
+ // for the signalling Task<TResult> (aka. promise). In this mode,
+ // we don't call InnerInvoke() in response to a Wait(), but simply wait on
+ // the completion event which will be set when the Future class calls Finish().
+ // But the event would now be signalled if Cancel() is called
+
+
+ internal object m_stateObject; // A state object that can be optionally supplied, passed to action.
+ internal TaskScheduler m_taskScheduler; // The task scheduler this task runs under.
+
+ internal volatile int m_stateFlags;
+
+ private Task ParentForDebugger => m_contingentProperties?.m_parent; // Private property used by a debugger to access this Task's parent
+ private int StateFlagsForDebugger => m_stateFlags; // Private property used by a debugger to access this Task's state flags
+
+ // State constants for m_stateFlags;
+ // The bits of m_stateFlags are allocated as follows:
+ // 0x40000000 - TaskBase state flag
+ // 0x3FFF0000 - Task state flags
+ // 0x0000FF00 - internal TaskCreationOptions flags
+ // 0x000000FF - publicly exposed TaskCreationOptions flags
+ //
+ // See TaskCreationOptions for bit values associated with TaskCreationOptions
+ //
+ private const int OptionsMask = 0xFFFF; // signifies the Options portion of m_stateFlags bin: 0000 0000 0000 0000 1111 1111 1111 1111
+ internal const int TASK_STATE_STARTED = 0x10000; //bin: 0000 0000 0000 0001 0000 0000 0000 0000
+ internal const int TASK_STATE_DELEGATE_INVOKED = 0x20000; //bin: 0000 0000 0000 0010 0000 0000 0000 0000
+ internal const int TASK_STATE_DISPOSED = 0x40000; //bin: 0000 0000 0000 0100 0000 0000 0000 0000
+ internal const int TASK_STATE_EXCEPTIONOBSERVEDBYPARENT = 0x80000; //bin: 0000 0000 0000 1000 0000 0000 0000 0000
+ internal const int TASK_STATE_CANCELLATIONACKNOWLEDGED = 0x100000; //bin: 0000 0000 0001 0000 0000 0000 0000 0000
+ internal const int TASK_STATE_FAULTED = 0x200000; //bin: 0000 0000 0010 0000 0000 0000 0000 0000
+ internal const int TASK_STATE_CANCELED = 0x400000; //bin: 0000 0000 0100 0000 0000 0000 0000 0000
+ internal const int TASK_STATE_WAITING_ON_CHILDREN = 0x800000; //bin: 0000 0000 1000 0000 0000 0000 0000 0000
+ internal const int TASK_STATE_RAN_TO_COMPLETION = 0x1000000; //bin: 0000 0001 0000 0000 0000 0000 0000 0000
+ internal const int TASK_STATE_WAITINGFORACTIVATION = 0x2000000; //bin: 0000 0010 0000 0000 0000 0000 0000 0000
+ internal const int TASK_STATE_COMPLETION_RESERVED = 0x4000000; //bin: 0000 0100 0000 0000 0000 0000 0000 0000
+ internal const int TASK_STATE_THREAD_WAS_ABORTED = 0x8000000; //bin: 0000 1000 0000 0000 0000 0000 0000 0000
+ internal const int TASK_STATE_WAIT_COMPLETION_NOTIFICATION = 0x10000000; //bin: 0001 0000 0000 0000 0000 0000 0000 0000
+ //This could be moved to InternalTaskOptions enum
+ internal const int TASK_STATE_EXECUTIONCONTEXT_IS_NULL = 0x20000000; //bin: 0010 0000 0000 0000 0000 0000 0000 0000
+ internal const int TASK_STATE_TASKSCHEDULED_WAS_FIRED = 0x40000000; //bin: 0100 0000 0000 0000 0000 0000 0000 0000
+
+ // A mask for all of the final states a task may be in
+ private const int TASK_STATE_COMPLETED_MASK = TASK_STATE_CANCELED | TASK_STATE_FAULTED | TASK_STATE_RAN_TO_COMPLETION;
+
+ // Values for ContingentProperties.m_internalCancellationRequested.
+ private const int CANCELLATION_REQUESTED = 0x1;
+
+ // Can be null, a single continuation, a list of continuations, or s_taskCompletionSentinel,
+ // in that order. The logic arround this object assumes it will never regress to a previous state.
+ private volatile object m_continuationObject = null;
+
+ // m_continuationObject is set to this when the task completes.
+ private static readonly object s_taskCompletionSentinel = new object();
+
+ // A private flag that would be set (only) by the debugger
+ // When true the Async Causality logging trace is enabled as well as a dictionary to relate operation ids with Tasks
+ [FriendAccessAllowed]
+ internal static bool s_asyncDebuggingEnabled; //false by default
+
+ // This dictonary relates the task id, from an operation id located in the Async Causality log to the actual
+ // task. This is to be used by the debugger ONLY. Task in this dictionary represent current active tasks.
+ private static readonly Dictionary<int, Task> s_currentActiveTasks = new Dictionary<int, Task>();
+ private static readonly Object s_activeTasksLock = new Object();
+
+ // These methods are a way to access the dictionary both from this class and for other classes that also
+ // activate dummy tasks. Specifically the AsyncTaskMethodBuilder and AsyncTaskMethodBuilder<>
+ [FriendAccessAllowed]
+ internal static bool AddToActiveTasks(Task task)
+ {
+ Contract.Requires(task != null, "Null Task objects can't be added to the ActiveTasks collection");
+ lock (s_activeTasksLock)
+ {
+ s_currentActiveTasks[task.Id] = task;
+ }
+ //always return true to keep signature as bool for backwards compatibility
+ return true;
+ }
+
+ [FriendAccessAllowed]
+ internal static void RemoveFromActiveTasks(int taskId)
+ {
+ lock (s_activeTasksLock)
+ {
+ s_currentActiveTasks.Remove(taskId);
+ }
+ }
+
+ // We moved a number of Task properties into this class. The idea is that in most cases, these properties never
+ // need to be accessed during the life cycle of a Task, so we don't want to instantiate them every time. Once
+ // one of these properties needs to be written, we will instantiate a ContingentProperties object and set
+ // the appropriate property.
+ internal class ContingentProperties
+ {
+ // Additional context
+
+ internal ExecutionContext m_capturedContext; // The execution context to run the task within, if any. Only set from non-concurrent contexts.
+
+ // Completion fields (exceptions and event)
+
+ internal volatile ManualResetEventSlim m_completionEvent; // Lazily created if waiting is required.
+ internal volatile TaskExceptionHolder m_exceptionsHolder; // Tracks exceptions, if any have occurred
+
+ // Cancellation fields (token, registration, and internally requested)
+
+ internal CancellationToken m_cancellationToken; // Task's cancellation token, if it has one
+ internal Shared<CancellationTokenRegistration> m_cancellationRegistration; // Task's registration with the cancellation token
+ internal volatile int m_internalCancellationRequested; // Its own field because multiple threads legally try to set it.
+
+ // Parenting fields
+
+ // # of active children + 1 (for this task itself).
+ // Used for ensuring all children are done before this task can complete
+ // The extra count helps prevent the race condition for executing the final state transition
+ // (i.e. whether the last child or this task itself should call FinishStageTwo())
+ internal volatile int m_completionCountdown = 1;
+ // A list of child tasks that threw an exception (TCEs don't count),
+ // but haven't yet been waited on by the parent, lazily initialized.
+ internal volatile List<Task> m_exceptionalChildren;
+ // A task's parent, or null if parent-less. Only set during Task construction.
+ internal Task m_parent;
+
+ /// <summary>
+ /// Sets the internal completion event.
+ /// </summary>
+ internal void SetCompleted()
+ {
+ var mres = m_completionEvent;
+ if (mres != null) mres.Set();
+ }
+
+ /// <summary>
+ /// Checks if we registered a CT callback during construction, and deregisters it.
+ /// This should be called when we know the registration isn't useful anymore. Specifically from Finish() if the task has completed
+ /// successfully or with an exception.
+ /// </summary>
+ internal void DeregisterCancellationCallback()
+ {
+ if (m_cancellationRegistration != null)
+ {
+ // Harden against ODEs thrown from disposing of the CTR.
+ // Since the task has already been put into a final state by the time this
+ // is called, all we can do here is suppress the exception.
+ try { m_cancellationRegistration.Value.Dispose(); }
+ catch (ObjectDisposedException) { }
+ m_cancellationRegistration = null;
+ }
+ }
+ }
+
+
+ // This field will only be instantiated to some non-null value if any ContingentProperties need to be set.
+ // This will be a ContingentProperties instance or a type derived from it
+ internal ContingentProperties m_contingentProperties;
+
+ // Special internal constructor to create an already-completed task.
+ // if canceled==true, create a Canceled task, or else create a RanToCompletion task.
+ // Constructs the task as already completed
+ internal Task(bool canceled, TaskCreationOptions creationOptions, CancellationToken ct)
+ {
+ int optionFlags = (int)creationOptions;
+ if (canceled)
+ {
+ m_stateFlags = TASK_STATE_CANCELED | TASK_STATE_CANCELLATIONACKNOWLEDGED | optionFlags;
+ m_contingentProperties = new ContingentProperties() // can't have children, so just instantiate directly
+ {
+ m_cancellationToken = ct,
+ m_internalCancellationRequested = CANCELLATION_REQUESTED,
+ };
+ }
+ else
+ m_stateFlags = TASK_STATE_RAN_TO_COMPLETION | optionFlags;
+ }
+
+ /// <summary>Constructor for use with promise-style tasks that aren't configurable.</summary>
+ internal Task()
+ {
+ m_stateFlags = TASK_STATE_WAITINGFORACTIVATION | (int)InternalTaskOptions.PromiseTask;
+ }
+
+ // Special constructor for use with promise-style tasks.
+ // Added promiseStyle parameter as an aid to the compiler to distinguish between (state,TCO) and
+ // (action,TCO). It should always be true.
+ internal Task(object state, TaskCreationOptions creationOptions, bool promiseStyle)
+ {
+ Contract.Assert(promiseStyle, "Promise CTOR: promiseStyle was false");
+
+ // Check the creationOptions. We allow the AttachedToParent option to be specified for promise tasks.
+ // Also allow RunContinuationsAsynchronously because this is the constructor called by TCS
+ if ((creationOptions & ~(TaskCreationOptions.AttachedToParent | TaskCreationOptions.RunContinuationsAsynchronously)) != 0)
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.creationOptions);
+ }
+
+ // Only set a parent if AttachedToParent is specified.
+ if ((creationOptions & TaskCreationOptions.AttachedToParent) != 0)
+ {
+ Task parent = Task.InternalCurrent;
+ if (parent != null)
+ {
+ EnsureContingentPropertiesInitializedUnsafe().m_parent = parent;
+ }
+ }
+
+ TaskConstructorCore(null, state, default(CancellationToken), creationOptions, InternalTaskOptions.PromiseTask, null);
+ }
+
+ /// <summary>
+ /// Initializes a new <see cref="Task"/> with the specified action.
+ /// </summary>
+ /// <param name="action">The delegate that represents the code to execute in the Task.</param>
+ /// <exception cref="T:System.ArgumentNullException">The <paramref name="action"/> argument is null.</exception>
+ [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable
+ public Task(Action action)
+ : this(action, null, null, default(CancellationToken), TaskCreationOptions.None, InternalTaskOptions.None, null)
+ {
+ StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
+ PossiblyCaptureContext(ref stackMark);
+ }
+
+ /// <summary>
+ /// Initializes a new <see cref="Task"/> with the specified action and <see cref="System.Threading.CancellationToken">CancellationToken</see>.
+ /// </summary>
+ /// <param name="action">The delegate that represents the code to execute in the Task.</param>
+ /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken">CancellationToken</see>
+ /// that will be assigned to the new Task.</param>
+ /// <exception cref="T:System.ArgumentNullException">The <paramref name="action"/> argument is null.</exception>
+ /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see>
+ /// has already been disposed.
+ /// </exception>
+ [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable
+ public Task(Action action, CancellationToken cancellationToken)
+ : this(action, null, null, cancellationToken, TaskCreationOptions.None, InternalTaskOptions.None, null)
+ {
+ StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
+ PossiblyCaptureContext(ref stackMark);
+ }
+
+ /// <summary>
+ /// Initializes a new <see cref="Task"/> with the specified action and creation options.
+ /// </summary>
+ /// <param name="action">The delegate that represents the code to execute in the task.</param>
+ /// <param name="creationOptions">
+ /// The <see cref="System.Threading.Tasks.TaskCreationOptions">TaskCreationOptions</see> used to
+ /// customize the Task's behavior.
+ /// </param>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="action"/> argument is null.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentOutOfRangeException">
+ /// The <paramref name="creationOptions"/> argument specifies an invalid value for <see
+ /// cref="T:System.Threading.Tasks.TaskCreationOptions"/>.
+ /// </exception>
+ [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable
+ public Task(Action action, TaskCreationOptions creationOptions)
+ : this(action, null, Task.InternalCurrentIfAttached(creationOptions), default(CancellationToken), creationOptions, InternalTaskOptions.None, null)
+ {
+ StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
+ PossiblyCaptureContext(ref stackMark);
+ }
+
+ /// <summary>
+ /// Initializes a new <see cref="Task"/> with the specified action and creation options.
+ /// </summary>
+ /// <param name="action">The delegate that represents the code to execute in the task.</param>
+ /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new task.</param>
+ /// <param name="creationOptions">
+ /// The <see cref="System.Threading.Tasks.TaskCreationOptions">TaskCreationOptions</see> used to
+ /// customize the Task's behavior.
+ /// </param>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="action"/> argument is null.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentOutOfRangeException">
+ /// The <paramref name="creationOptions"/> argument specifies an invalid value for <see
+ /// cref="T:System.Threading.Tasks.TaskCreationOptions"/>.
+ /// </exception>
+ /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see>
+ /// has already been disposed.
+ /// </exception>
+ [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable
+ public Task(Action action, CancellationToken cancellationToken, TaskCreationOptions creationOptions)
+ : this(action, null, Task.InternalCurrentIfAttached(creationOptions), cancellationToken, creationOptions, InternalTaskOptions.None, null)
+ {
+ StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
+ PossiblyCaptureContext(ref stackMark);
+ }
+
+
+ /// <summary>
+ /// Initializes a new <see cref="Task"/> with the specified action and state.
+ /// </summary>
+ /// <param name="action">The delegate that represents the code to execute in the task.</param>
+ /// <param name="state">An object representing data to be used by the action.</param>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="action"/> argument is null.
+ /// </exception>
+ [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable
+ public Task(Action<object> action, object state)
+ : this(action, state, null, default(CancellationToken), TaskCreationOptions.None, InternalTaskOptions.None, null)
+ {
+ StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
+ PossiblyCaptureContext(ref stackMark);
+ }
+
+ /// <summary>
+ /// Initializes a new <see cref="Task"/> with the specified action, state, snd options.
+ /// </summary>
+ /// <param name="action">The delegate that represents the code to execute in the task.</param>
+ /// <param name="state">An object representing data to be used by the action.</param>
+ /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new task.</param>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="action"/> argument is null.
+ /// </exception>
+ /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see>
+ /// has already been disposed.
+ /// </exception>
+ [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable
+ public Task(Action<object> action, object state, CancellationToken cancellationToken)
+ : this(action, state, null, cancellationToken, TaskCreationOptions.None, InternalTaskOptions.None, null)
+ {
+ StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
+ PossiblyCaptureContext(ref stackMark);
+ }
+
+ /// <summary>
+ /// Initializes a new <see cref="Task"/> with the specified action, state, snd options.
+ /// </summary>
+ /// <param name="action">The delegate that represents the code to execute in the task.</param>
+ /// <param name="state">An object representing data to be used by the action.</param>
+ /// <param name="creationOptions">
+ /// The <see cref="System.Threading.Tasks.TaskCreationOptions">TaskCreationOptions</see> used to
+ /// customize the Task's behavior.
+ /// </param>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="action"/> argument is null.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentOutOfRangeException">
+ /// The <paramref name="creationOptions"/> argument specifies an invalid value for <see
+ /// cref="T:System.Threading.Tasks.TaskCreationOptions"/>.
+ /// </exception>
+ [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable
+ public Task(Action<object> action, object state, TaskCreationOptions creationOptions)
+ : this(action, state, Task.InternalCurrentIfAttached(creationOptions), default(CancellationToken), creationOptions, InternalTaskOptions.None, null)
+ {
+ StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
+ PossiblyCaptureContext(ref stackMark);
+ }
+
+ /// <summary>
+ /// Initializes a new <see cref="Task"/> with the specified action, state, snd options.
+ /// </summary>
+ /// <param name="action">The delegate that represents the code to execute in the task.</param>
+ /// <param name="state">An object representing data to be used by the action.</param>
+ /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new task.</param>
+ /// <param name="creationOptions">
+ /// The <see cref="System.Threading.Tasks.TaskCreationOptions">TaskCreationOptions</see> used to
+ /// customize the Task's behavior.
+ /// </param>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="action"/> argument is null.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentOutOfRangeException">
+ /// The <paramref name="creationOptions"/> argument specifies an invalid value for <see
+ /// cref="T:System.Threading.Tasks.TaskCreationOptions"/>.
+ /// </exception>
+ /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see>
+ /// has already been disposed.
+ /// </exception>
+ [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable
+ public Task(Action<object> action, object state, CancellationToken cancellationToken, TaskCreationOptions creationOptions)
+ : this(action, state, Task.InternalCurrentIfAttached(creationOptions), cancellationToken, creationOptions, InternalTaskOptions.None, null)
+ {
+ StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
+ PossiblyCaptureContext(ref stackMark);
+ }
+
+ internal Task(Action<object> action, object state, Task parent, CancellationToken cancellationToken,
+ TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, TaskScheduler scheduler, ref StackCrawlMark stackMark)
+ : this(action, state, parent, cancellationToken, creationOptions, internalOptions, scheduler)
+ {
+ PossiblyCaptureContext(ref stackMark);
+ }
+
+ /// <summary>
+ /// An internal constructor used by the factory methods on task and its descendent(s).
+ /// This variant does not capture the ExecutionContext; it is up to the caller to do that.
+ /// </summary>
+ /// <param name="action">An action to execute.</param>
+ /// <param name="state">Optional state to pass to the action.</param>
+ /// <param name="parent">Parent of Task.</param>
+ /// <param name="cancellationToken">A CancellationToken for the task.</param>
+ /// <param name="scheduler">A task scheduler under which the task will run.</param>
+ /// <param name="creationOptions">Options to control its execution.</param>
+ /// <param name="internalOptions">Internal options to control its execution</param>
+ internal Task(Delegate action, object state, Task parent, CancellationToken cancellationToken,
+ TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, TaskScheduler scheduler)
+ {
+ if (action == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.action);
+ }
+ Contract.EndContractBlock();
+
+ // Keep a link to your parent if: (A) You are attached, or (B) you are self-replicating.
+ if (parent != null &&
+ ((creationOptions & TaskCreationOptions.AttachedToParent) != 0 ||
+ (internalOptions & InternalTaskOptions.SelfReplicating) != 0))
+ {
+ EnsureContingentPropertiesInitializedUnsafe().m_parent = parent;
+ }
+
+ TaskConstructorCore(action, state, cancellationToken, creationOptions, internalOptions, scheduler);
+ }
+
+ /// <summary>
+ /// Common logic used by the following internal ctors:
+ /// Task()
+ /// Task(object action, object state, Task parent, TaskCreationOptions options, TaskScheduler taskScheduler)
+ /// </summary>
+ /// <param name="action">Action for task to execute.</param>
+ /// <param name="state">Object to which to pass to action (may be null)</param>
+ /// <param name="scheduler">Task scheduler on which to run thread (only used by continuation tasks).</param>
+ /// <param name="cancellationToken">A CancellationToken for the Task.</param>
+ /// <param name="creationOptions">Options to customize behavior of Task.</param>
+ /// <param name="internalOptions">Internal options to customize behavior of Task.</param>
+ internal void TaskConstructorCore(object action, object state, CancellationToken cancellationToken,
+ TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, TaskScheduler scheduler)
+ {
+ m_action = action;
+ m_stateObject = state;
+ m_taskScheduler = scheduler;
+
+ // Check for validity of options
+ if ((creationOptions &
+ ~(TaskCreationOptions.AttachedToParent |
+ TaskCreationOptions.LongRunning |
+ TaskCreationOptions.DenyChildAttach |
+ TaskCreationOptions.HideScheduler |
+ TaskCreationOptions.PreferFairness |
+ TaskCreationOptions.RunContinuationsAsynchronously)) != 0)
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.creationOptions);
+ }
+
+#if DEBUG
+ // Check the validity of internalOptions
+ int illegalInternalOptions =
+ (int) (internalOptions &
+ ~(InternalTaskOptions.SelfReplicating |
+ InternalTaskOptions.ChildReplica |
+ InternalTaskOptions.PromiseTask |
+ InternalTaskOptions.ContinuationTask |
+ InternalTaskOptions.LazyCancellation |
+ InternalTaskOptions.QueuedByRuntime));
+ Contract.Assert(illegalInternalOptions == 0, "TaskConstructorCore: Illegal internal options");
+#endif
+
+ // Throw exception if the user specifies both LongRunning and SelfReplicating
+ if (((creationOptions & TaskCreationOptions.LongRunning) != 0) &&
+ ((internalOptions & InternalTaskOptions.SelfReplicating) != 0))
+ {
+ ThrowHelper.ThrowInvalidOperationException(ExceptionResource.Task_ctor_LRandSR);
+ }
+
+ // Assign options to m_stateAndOptionsFlag.
+ Contract.Assert(m_stateFlags == 0, "TaskConstructorCore: non-zero m_stateFlags");
+ Contract.Assert((((int)creationOptions) | OptionsMask) == OptionsMask, "TaskConstructorCore: options take too many bits");
+ var tmpFlags = (int)creationOptions | (int)internalOptions;
+ if ((m_action == null) || ((internalOptions & InternalTaskOptions.ContinuationTask) != 0))
+ {
+ // For continuation tasks or TaskCompletionSource.Tasks, begin life in the
+ // WaitingForActivation state rather than the Created state.
+ tmpFlags |= TASK_STATE_WAITINGFORACTIVATION;
+ }
+ m_stateFlags = tmpFlags; // one write to the volatile m_stateFlags instead of two when setting the above options
+
+ // Now is the time to add the new task to the children list
+ // of the creating task if the options call for it.
+ // We can safely call the creator task's AddNewChild() method to register it,
+ // because at this point we are already on its thread of execution.
+
+ Task parent = m_contingentProperties?.m_parent;
+ if (parent != null
+ && ((creationOptions & TaskCreationOptions.AttachedToParent) != 0)
+ && ((parent.CreationOptions & TaskCreationOptions.DenyChildAttach) == 0)
+ )
+ {
+ parent.AddNewChild();
+ }
+
+ // if we have a non-null cancellationToken, allocate the contingent properties to save it
+ // we need to do this as the very last thing in the construction path, because the CT registration could modify m_stateFlags
+ if (cancellationToken.CanBeCanceled)
+ {
+ Contract.Assert((internalOptions &
+ (InternalTaskOptions.ChildReplica | InternalTaskOptions.SelfReplicating | InternalTaskOptions.ContinuationTask)) == 0,
+ "TaskConstructorCore: Did not expect to see cancelable token for replica/replicating or continuation task.");
+
+ AssignCancellationToken(cancellationToken, null, null);
+ }
+ }
+
+ /// <summary>
+ /// Handles everything needed for associating a CancellationToken with a task which is being constructed.
+ /// This method is meant to be be called either from the TaskConstructorCore or from ContinueWithCore.
+ /// </summary>
+ private void AssignCancellationToken(CancellationToken cancellationToken, Task antecedent, TaskContinuation continuation)
+ {
+ // There is no need to worry about concurrency issues here because we are in the constructor path of the task --
+ // there should not be any race conditions to set m_contingentProperties at this point.
+ ContingentProperties props = EnsureContingentPropertiesInitializedUnsafe();
+ props.m_cancellationToken = cancellationToken;
+
+ try
+ {
+ if (AppContextSwitches.ThrowExceptionIfDisposedCancellationTokenSource)
+ {
+ cancellationToken.ThrowIfSourceDisposed();
+ }
+
+ // If an unstarted task has a valid CancellationToken that gets signalled while the task is still not queued
+ // we need to proactively cancel it, because it may never execute to transition itself.
+ // The only way to accomplish this is to register a callback on the CT.
+ // We exclude Promise tasks from this, because TaskCompletionSource needs to fully control the inner tasks's lifetime (i.e. not allow external cancellations)
+ if ((((InternalTaskOptions)Options &
+ (InternalTaskOptions.QueuedByRuntime | InternalTaskOptions.PromiseTask | InternalTaskOptions.LazyCancellation)) == 0))
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ // Fast path for an already-canceled cancellationToken
+ this.InternalCancel(false);
+ }
+ else
+ {
+ // Regular path for an uncanceled cancellationToken
+ CancellationTokenRegistration ctr;
+ if (antecedent == null)
+ {
+ // if no antecedent was specified, use this task's reference as the cancellation state object
+ ctr = cancellationToken.InternalRegisterWithoutEC(s_taskCancelCallback, this);
+ }
+ else
+ {
+ // If an antecedent was specified, pack this task, its antecedent and the TaskContinuation together as a tuple
+ // and use it as the cancellation state object. This will be unpacked in the cancellation callback so that
+ // antecedent.RemoveCancellation(continuation) can be invoked.
+ ctr = cancellationToken.InternalRegisterWithoutEC(s_taskCancelCallback,
+ new Tuple<Task, Task, TaskContinuation>(this, antecedent, continuation));
+ }
+
+ props.m_cancellationRegistration = new Shared<CancellationTokenRegistration>(ctr);
+ }
+ }
+ }
+ catch
+ {
+ // If we have an exception related to our CancellationToken, then we need to subtract ourselves
+ // from our parent before throwing it.
+ Task parent = m_contingentProperties?.m_parent;
+ if ((parent != null) &&
+ ((Options & TaskCreationOptions.AttachedToParent) != 0)
+ && ((parent.Options & TaskCreationOptions.DenyChildAttach) == 0))
+ {
+ parent.DisregardChild();
+ }
+ throw;
+ }
+ }
+
+
+ // Static delegate to be used as a cancellation callback on unstarted tasks that have a valid cancellation token.
+ // This is necessary to transition them into canceled state if their cancellation token is signalled while they are still not queued
+ private readonly static Action<Object> s_taskCancelCallback = new Action<Object>(TaskCancelCallback);
+ private static void TaskCancelCallback(Object o)
+ {
+ var targetTask = o as Task;
+ if (targetTask == null)
+ {
+ var tuple = o as Tuple<Task, Task, TaskContinuation>;
+ if (tuple != null)
+ {
+ targetTask = tuple.Item1;
+
+ Task antecedentTask = tuple.Item2;
+ TaskContinuation continuation = tuple.Item3;
+ antecedentTask.RemoveContinuation(continuation);
+ }
+ }
+ Contract.Assert(targetTask != null,
+ "targetTask should have been non-null, with the supplied argument being a task or a tuple containing one");
+ targetTask.InternalCancel(false);
+ }
+
+ // Debugger support
+ private string DebuggerDisplayMethodDescription
+ {
+ get
+ {
+ Delegate d = (Delegate)m_action;
+ return d != null ? d.Method.ToString() : "{null}";
+ }
+ }
+
+
+ /// <summary>
+ /// Captures the ExecutionContext so long as flow isn't suppressed.
+ /// </summary>
+ /// <param name="stackMark">A stack crawl mark pointing to the frame of the caller.</param>
+
+ [SecuritySafeCritical]
+ internal void PossiblyCaptureContext(ref StackCrawlMark stackMark)
+ {
+ Contract.Assert(m_contingentProperties == null || m_contingentProperties.m_capturedContext == null,
+ "Captured an ExecutionContext when one was already captured.");
+
+ // In the legacy .NET 3.5 build, we don't have the optimized overload of Capture()
+ // available, so we call the parameterless overload.
+ CapturedContext = ExecutionContext.Capture(
+ ref stackMark,
+ ExecutionContext.CaptureOptions.IgnoreSyncCtx | ExecutionContext.CaptureOptions.OptimizeDefaultCase);
+ }
+
+ // Internal property to process TaskCreationOptions access and mutation.
+ internal TaskCreationOptions Options
+ {
+ get
+ {
+ int stateFlags = m_stateFlags; // "cast away" volatility to enable inlining of OptionsMethod
+ return OptionsMethod(stateFlags);
+ }
+ }
+
+ // Similar to Options property, but allows for the use of a cached flags value rather than
+ // a read of the volatile m_stateFlags field.
+ internal static TaskCreationOptions OptionsMethod(int flags)
+ {
+ Contract.Assert((OptionsMask & 1) == 1, "OptionsMask needs a shift in Options.get");
+ return (TaskCreationOptions)(flags & OptionsMask);
+ }
+
+ // Atomically OR-in newBits to m_stateFlags, while making sure that
+ // no illegalBits are set. Returns true on success, false on failure.
+ internal bool AtomicStateUpdate(int newBits, int illegalBits)
+ {
+ // This could be implemented in terms of:
+ // internal bool AtomicStateUpdate(int newBits, int illegalBits, ref int oldFlags);
+ // but for high-throughput perf, that delegation's cost is noticeable.
+
+ SpinWait sw = new SpinWait();
+ do
+ {
+ int oldFlags = m_stateFlags;
+ if ((oldFlags & illegalBits) != 0) return false;
+ if (Interlocked.CompareExchange(ref m_stateFlags, oldFlags | newBits, oldFlags) == oldFlags)
+ {
+ return true;
+ }
+ sw.SpinOnce();
+ } while (true);
+ }
+
+ internal bool AtomicStateUpdate(int newBits, int illegalBits, ref int oldFlags)
+ {
+ SpinWait sw = new SpinWait();
+ do
+ {
+ oldFlags = m_stateFlags;
+ if ((oldFlags & illegalBits) != 0) return false;
+ if (Interlocked.CompareExchange(ref m_stateFlags, oldFlags | newBits, oldFlags) == oldFlags)
+ {
+ return true;
+ }
+ sw.SpinOnce();
+ } while (true);
+ }
+
+ /// <summary>
+ /// Sets or clears the TASK_STATE_WAIT_COMPLETION_NOTIFICATION state bit.
+ /// The debugger sets this bit to aid it in "stepping out" of an async method body.
+ /// If enabled is true, this must only be called on a task that has not yet been completed.
+ /// If enabled is false, this may be called on completed tasks.
+ /// Either way, it should only be used for promise-style tasks.
+ /// </summary>
+ /// <param name="enabled">true to set the bit; false to unset the bit.</param>
+ internal void SetNotificationForWaitCompletion(bool enabled)
+ {
+ Contract.Assert((Options & (TaskCreationOptions)InternalTaskOptions.PromiseTask) != 0,
+ "Should only be used for promise-style tasks"); // hasn't been vetted on other kinds as there hasn't been a need
+
+ if (enabled)
+ {
+ // Atomically set the END_AWAIT_NOTIFICATION bit
+ bool success = AtomicStateUpdate(TASK_STATE_WAIT_COMPLETION_NOTIFICATION,
+ TASK_STATE_COMPLETED_MASK | TASK_STATE_COMPLETION_RESERVED);
+ Contract.Assert(success, "Tried to set enabled on completed Task");
+ }
+ else
+ {
+ // Atomically clear the END_AWAIT_NOTIFICATION bit
+ SpinWait sw = new SpinWait();
+ while (true)
+ {
+ int oldFlags = m_stateFlags;
+ int newFlags = oldFlags & (~TASK_STATE_WAIT_COMPLETION_NOTIFICATION);
+ if (Interlocked.CompareExchange(ref m_stateFlags, newFlags, oldFlags) == oldFlags) break;
+ sw.SpinOnce();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Calls the debugger notification method if the right bit is set and if
+ /// the task itself allows for the notification to proceed.
+ /// </summary>
+ /// <returns>true if the debugger was notified; otherwise, false.</returns>
+ internal bool NotifyDebuggerOfWaitCompletionIfNecessary()
+ {
+ // Notify the debugger if of any of the tasks we've waited on requires notification
+ if (IsWaitNotificationEnabled && ShouldNotifyDebuggerOfWaitCompletion)
+ {
+ NotifyDebuggerOfWaitCompletion();
+ return true;
+ }
+ return false;
+ }
+
+ /// <summary>Returns true if any of the supplied tasks require wait notification.</summary>
+ /// <param name="tasks">The tasks to check.</param>
+ /// <returns>true if any of the tasks require notification; otherwise, false.</returns>
+ internal static bool AnyTaskRequiresNotifyDebuggerOfWaitCompletion(Task[] tasks)
+ {
+ Contract.Assert(tasks != null, "Expected non-null array of tasks");
+ foreach (var task in tasks)
+ {
+ if (task != null &&
+ task.IsWaitNotificationEnabled &&
+ task.ShouldNotifyDebuggerOfWaitCompletion) // potential recursion
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /// <summary>Gets whether either the end await bit is set or (not xor) the task has not completed successfully.</summary>
+ /// <returns>(DebuggerBitSet || !RanToCompletion)</returns>
+ internal bool IsWaitNotificationEnabledOrNotRanToCompletion
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ return (m_stateFlags & (Task.TASK_STATE_WAIT_COMPLETION_NOTIFICATION | Task.TASK_STATE_RAN_TO_COMPLETION))
+ != Task.TASK_STATE_RAN_TO_COMPLETION;
+ }
+ }
+
+ /// <summary>
+ /// Determines whether we should inform the debugger that we're ending a join with a task.
+ /// This should only be called if the debugger notification bit is set, as it is has some cost,
+ /// namely it is a virtual call (however calling it if the bit is not set is not functionally
+ /// harmful). Derived implementations may choose to only conditionally call down to this base
+ /// implementation.
+ /// </summary>
+ internal virtual bool ShouldNotifyDebuggerOfWaitCompletion // ideally would be familyAndAssembly, but that can't be done in C#
+ {
+ get
+ {
+ // It's theoretically possible but extremely rare that this assert could fire because the
+ // bit was unset between the time that it was checked and this method was called.
+ // It's so remote a chance that it's worth having the assert to protect against misuse.
+ bool isWaitNotificationEnabled = IsWaitNotificationEnabled;
+ Contract.Assert(isWaitNotificationEnabled, "Should only be called if the wait completion bit is set.");
+ return isWaitNotificationEnabled;
+ }
+ }
+
+ /// <summary>Gets whether the task's debugger notification for wait completion bit is set.</summary>
+ /// <returns>true if the bit is set; false if it's not set.</returns>
+ internal bool IsWaitNotificationEnabled // internal only to enable unit tests; would otherwise be private
+ {
+ get { return (m_stateFlags & TASK_STATE_WAIT_COMPLETION_NOTIFICATION) != 0; }
+ }
+
+ /// <summary>Placeholder method used as a breakpoint target by the debugger. Must not be inlined or optimized.</summary>
+ /// <remarks>All joins with a task should end up calling this if their debugger notification bit is set.</remarks>
+ [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
+ private void NotifyDebuggerOfWaitCompletion()
+ {
+ // It's theoretically possible but extremely rare that this assert could fire because the
+ // bit was unset between the time that it was checked and this method was called.
+ // It's so remote a chance that it's worth having the assert to protect against misuse.
+ Contract.Assert(IsWaitNotificationEnabled, "Should only be called if the wait completion bit is set.");
+
+ // Now that we're notifying the debugger, clear the bit. The debugger should do this anyway,
+ // but this adds a bit of protection in case it fails to, and given that the debugger is involved,
+ // the overhead here for the interlocked is negligable. We do still rely on the debugger
+ // to clear bits, as this doesn't recursively clear bits in the case of, for example, WhenAny.
+ SetNotificationForWaitCompletion(enabled: false);
+ }
+
+
+ // Atomically mark a Task as started while making sure that it is not canceled.
+ internal bool MarkStarted()
+ {
+ return AtomicStateUpdate(TASK_STATE_STARTED, TASK_STATE_CANCELED | TASK_STATE_STARTED);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal bool FireTaskScheduledIfNeeded(TaskScheduler ts)
+ {
+ var etwLog = TplEtwProvider.Log;
+ if (etwLog.IsEnabled() && (m_stateFlags & Task.TASK_STATE_TASKSCHEDULED_WAS_FIRED) == 0)
+ {
+ m_stateFlags |= Task.TASK_STATE_TASKSCHEDULED_WAS_FIRED;
+
+ Task currentTask = Task.InternalCurrent;
+ Task parentTask = m_contingentProperties?.m_parent;
+ etwLog.TaskScheduled(ts.Id, currentTask == null ? 0 : currentTask.Id,
+ this.Id, parentTask == null ? 0 : parentTask.Id, (int)this.Options,
+ System.Threading.Thread.GetDomainID());
+ return true;
+ }
+ else
+ return false;
+ }
+
+ /// <summary>
+ /// Internal function that will be called by a new child task to add itself to
+ /// the children list of the parent (this).
+ ///
+ /// Since a child task can only be created from the thread executing the action delegate
+ /// of this task, reentrancy is neither required nor supported. This should not be called from
+ /// anywhere other than the task construction/initialization codepaths.
+ /// </summary>
+ internal void AddNewChild()
+ {
+ Contract.Assert(Task.InternalCurrent == this || this.IsSelfReplicatingRoot, "Task.AddNewChild(): Called from an external context");
+
+ var props = EnsureContingentPropertiesInitialized();
+
+ if (props.m_completionCountdown == 1 && !IsSelfReplicatingRoot)
+ {
+ // A count of 1 indicates so far there was only the parent, and this is the first child task
+ // Single kid => no fuss about who else is accessing the count. Let's save ourselves 100 cycles
+ // We exclude self replicating root tasks from this optimization, because further child creation can take place on
+ // other cores and with bad enough timing this write may not be visible to them.
+ props.m_completionCountdown++;
+ }
+ else
+ {
+ // otherwise do it safely
+ Interlocked.Increment(ref props.m_completionCountdown);
+ }
+ }
+
+ // This is called in the case where a new child is added, but then encounters a CancellationToken-related exception.
+ // We need to subtract that child from m_completionCountdown, or the parent will never complete.
+ internal void DisregardChild()
+ {
+ Contract.Assert(Task.InternalCurrent == this, "Task.DisregardChild(): Called from an external context");
+
+ var props = EnsureContingentPropertiesInitialized();
+ Contract.Assert(props.m_completionCountdown >= 2, "Task.DisregardChild(): Expected parent count to be >= 2");
+ Interlocked.Decrement(ref props.m_completionCountdown);
+ }
+
+ /// <summary>
+ /// Starts the <see cref="Task"/>, scheduling it for execution to the current <see
+ /// cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see>.
+ /// </summary>
+ /// <remarks>
+ /// A task may only be started and run only once. Any attempts to schedule a task a second time
+ /// will result in an exception.
+ /// </remarks>
+ /// <exception cref="InvalidOperationException">
+ /// The <see cref="Task"/> is not in a valid state to be started. It may have already been started,
+ /// executed, or canceled, or it may have been created in a manner that doesn't support direct
+ /// scheduling.
+ /// </exception>
+ public void Start()
+ {
+ Start(TaskScheduler.Current);
+ }
+
+ /// <summary>
+ /// Starts the <see cref="Task"/>, scheduling it for execution to the specified <see
+ /// cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see>.
+ /// </summary>
+ /// <remarks>
+ /// A task may only be started and run only once. Any attempts to schedule a task a second time will
+ /// result in an exception.
+ /// </remarks>
+ /// <param name="scheduler">
+ /// The <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> with which to associate
+ /// and execute this task.
+ /// </param>
+ /// <exception cref="ArgumentNullException">
+ /// The <paramref name="scheduler"/> argument is null.
+ /// </exception>
+ /// <exception cref="InvalidOperationException">
+ /// The <see cref="Task"/> is not in a valid state to be started. It may have already been started,
+ /// executed, or canceled, or it may have been created in a manner that doesn't support direct
+ /// scheduling.
+ /// </exception>
+ public void Start(TaskScheduler scheduler)
+ {
+ // Read the volatile m_stateFlags field once and cache it for subsequent operations
+ int flags = m_stateFlags;
+
+ // Need to check this before (m_action == null) because completed tasks will
+ // set m_action to null. We would want to know if this is the reason that m_action == null.
+ if (IsCompletedMethod(flags))
+ {
+ ThrowHelper.ThrowInvalidOperationException(ExceptionResource.Task_Start_TaskCompleted);
+ }
+
+ if (scheduler == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler);
+ }
+
+ var options = OptionsMethod(flags);
+ if ((options & (TaskCreationOptions)InternalTaskOptions.PromiseTask) != 0)
+ {
+ ThrowHelper.ThrowInvalidOperationException(ExceptionResource.Task_Start_Promise);
+ }
+ if ((options & (TaskCreationOptions)InternalTaskOptions.ContinuationTask) != 0)
+ {
+ ThrowHelper.ThrowInvalidOperationException(ExceptionResource.Task_Start_ContinuationTask);
+ }
+
+ // Make sure that Task only gets started once. Or else throw an exception.
+ if (Interlocked.CompareExchange(ref m_taskScheduler, scheduler, null) != null)
+ {
+ ThrowHelper.ThrowInvalidOperationException(ExceptionResource.Task_Start_AlreadyStarted);
+ }
+
+ ScheduleAndStart(true);
+ }
+
+ /// <summary>
+ /// Runs the <see cref="Task"/> synchronously on the current <see
+ /// cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see>.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// A task may only be started and run only once. Any attempts to schedule a task a second time will
+ /// result in an exception.
+ /// </para>
+ /// <para>
+ /// Tasks executed with <see cref="RunSynchronously()"/> will be associated with the current <see
+ /// cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see>.
+ /// </para>
+ /// <para>
+ /// If the target scheduler does not support running this Task on the current thread, the Task will
+ /// be scheduled for execution on the scheduler, and the current thread will block until the
+ /// Task has completed execution.
+ /// </para>
+ /// </remarks>
+ /// <exception cref="InvalidOperationException">
+ /// The <see cref="Task"/> is not in a valid state to be started. It may have already been started,
+ /// executed, or canceled, or it may have been created in a manner that doesn't support direct
+ /// scheduling.
+ /// </exception>
+ public void RunSynchronously()
+ {
+ InternalRunSynchronously(TaskScheduler.Current, waitForCompletion: true);
+ }
+
+ /// <summary>
+ /// Runs the <see cref="Task"/> synchronously on the <see
+ /// cref="System.Threading.Tasks.TaskScheduler">scheduler</see> provided.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// A task may only be started and run only once. Any attempts to schedule a task a second time will
+ /// result in an exception.
+ /// </para>
+ /// <para>
+ /// If the target scheduler does not support running this Task on the current thread, the Task will
+ /// be scheduled for execution on the scheduler, and the current thread will block until the
+ /// Task has completed execution.
+ /// </para>
+ /// </remarks>
+ /// <exception cref="InvalidOperationException">
+ /// The <see cref="Task"/> is not in a valid state to be started. It may have already been started,
+ /// executed, or canceled, or it may have been created in a manner that doesn't support direct
+ /// scheduling.
+ /// </exception>
+ /// <exception cref="ArgumentNullException">The <paramref name="scheduler"/> parameter
+ /// is null.</exception>
+ /// <param name="scheduler">The scheduler on which to attempt to run this task inline.</param>
+ public void RunSynchronously(TaskScheduler scheduler)
+ {
+ if (scheduler == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler);
+ }
+ Contract.EndContractBlock();
+
+ InternalRunSynchronously(scheduler, waitForCompletion: true);
+ }
+
+ //
+ // Internal version of RunSynchronously that allows not waiting for completion.
+ //
+ [SecuritySafeCritical] // Needed for QueueTask
+ internal void InternalRunSynchronously(TaskScheduler scheduler, bool waitForCompletion)
+ {
+ Contract.Requires(scheduler != null, "Task.InternalRunSynchronously(): null TaskScheduler");
+
+ // Read the volatile m_stateFlags field once and cache it for subsequent operations
+ int flags = m_stateFlags;
+
+ // Can't call this method on a continuation task
+ var options = OptionsMethod(flags);
+ if ((options & (TaskCreationOptions)InternalTaskOptions.ContinuationTask) != 0)
+ {
+ ThrowHelper.ThrowInvalidOperationException(ExceptionResource.Task_RunSynchronously_Continuation);
+ }
+
+ // Can't call this method on a promise-style task
+ if ((options & (TaskCreationOptions)InternalTaskOptions.PromiseTask) != 0)
+ {
+ ThrowHelper.ThrowInvalidOperationException(ExceptionResource.Task_RunSynchronously_Promise);
+ }
+
+ // Can't call this method on a task that has already completed
+ if (IsCompletedMethod(flags))
+ {
+ ThrowHelper.ThrowInvalidOperationException(ExceptionResource.Task_RunSynchronously_TaskCompleted);
+ }
+
+ // Make sure that Task only gets started once. Or else throw an exception.
+ if (Interlocked.CompareExchange(ref m_taskScheduler, scheduler, null) != null)
+ {
+ ThrowHelper.ThrowInvalidOperationException(ExceptionResource.Task_RunSynchronously_AlreadyStarted);
+ }
+
+ // execute only if we successfully cancel when concurrent cancel attempts are made.
+ // otherwise throw an exception, because we've been canceled.
+ if (MarkStarted())
+ {
+ bool taskQueued = false;
+ try
+ {
+ // We wrap TryRunInline() in a try/catch block and move an excepted task to Faulted here,
+ // but not in Wait()/WaitAll()/FastWaitAll(). Here, we know for sure that the
+ // task will not be subsequently scheduled (assuming that the scheduler adheres
+ // to the guideline that an exception implies that no state change took place),
+ // so it is safe to catch the exception and move the task to a final state. The
+ // same cannot be said for Wait()/WaitAll()/FastWaitAll().
+ if (!scheduler.TryRunInline(this, false))
+ {
+ scheduler.InternalQueueTask(this);
+ taskQueued = true; // only mark this after successfully queuing the task.
+ }
+
+ // A successful TryRunInline doesn't guarantee completion, as there may be unfinished children.
+ // Also if we queued the task above, the task may not be done yet.
+ if (waitForCompletion && !IsCompleted)
+ {
+ SpinThenBlockingWait(Timeout.Infinite, default(CancellationToken));
+ }
+ }
+ catch (Exception e)
+ {
+ // we 1) either received an unexpected exception originating from a custom scheduler, which needs to be wrapped in a TSE and thrown
+ // 2) or a a ThreadAbortException, which we need to skip here, because it would already have been handled in Task.Execute
+ if (!taskQueued && !(e is ThreadAbortException))
+ {
+ // We had a problem with TryRunInline() or QueueTask().
+ // Record the exception, marking ourselves as Completed/Faulted.
+ TaskSchedulerException tse = new TaskSchedulerException(e);
+ AddException(tse);
+ Finish(false);
+
+ // Mark ourselves as "handled" to avoid crashing the finalizer thread if the caller neglects to
+ // call Wait() on this task.
+ // m_contingentProperties.m_exceptionsHolder *should* already exist after AddException()
+ Contract.Assert(
+ (m_contingentProperties != null) &&
+ (m_contingentProperties.m_exceptionsHolder != null) &&
+ (m_contingentProperties.m_exceptionsHolder.ContainsFaultList),
+ "Task.InternalRunSynchronously(): Expected m_contingentProperties.m_exceptionsHolder to exist " +
+ "and to have faults recorded.");
+ m_contingentProperties.m_exceptionsHolder.MarkAsHandled(false);
+
+ // And re-throw.
+ throw tse;
+ }
+ // We had a problem with waiting or this is a thread abort. Just re-throw.
+ else throw;
+ }
+ }
+ else
+ {
+ Contract.Assert((m_stateFlags & TASK_STATE_CANCELED) != 0, "Task.RunSynchronously: expected TASK_STATE_CANCELED to be set");
+ // Can't call this method on canceled task.
+ ThrowHelper.ThrowInvalidOperationException(ExceptionResource.Task_RunSynchronously_TaskCompleted);
+ }
+ }
+
+
+ ////
+ //// Helper methods for Factory StartNew methods.
+ ////
+
+
+ // Implicitly converts action to object and handles the meat of the StartNew() logic.
+ internal static Task InternalStartNew(
+ Task creatingTask, Delegate action, object state, CancellationToken cancellationToken, TaskScheduler scheduler,
+ TaskCreationOptions options, InternalTaskOptions internalOptions, ref StackCrawlMark stackMark)
+ {
+ // Validate arguments.
+ if (scheduler == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler);
+ }
+ Contract.EndContractBlock();
+
+ // Create and schedule the task. This throws an InvalidOperationException if already shut down.
+ // Here we add the InternalTaskOptions.QueuedByRuntime to the internalOptions, so that TaskConstructorCore can skip the cancellation token registration
+ Task t = new Task(action, state, creatingTask, cancellationToken, options, internalOptions | InternalTaskOptions.QueuedByRuntime, scheduler);
+ t.PossiblyCaptureContext(ref stackMark);
+
+ t.ScheduleAndStart(false);
+ return t;
+ }
+
+ /// <summary>
+ /// Gets a unique ID for a <see cref="Task">Task</see> or task continuation instance.
+ /// </summary>
+ internal static int NewId()
+ {
+ int newId = 0;
+ // We need to repeat if Interlocked.Increment wraps around and returns 0.
+ // Otherwise next time this task's Id is queried it will get a new value
+ do
+ {
+ newId = Interlocked.Increment(ref s_taskIdCounter);
+ }
+ while (newId == 0);
+ TplEtwProvider.Log.NewID(newId);
+ return newId;
+ }
+
+
+ /////////////
+ // properties
+
+ /// <summary>
+ /// Gets a unique ID for this <see cref="Task">Task</see> instance.
+ /// </summary>
+ /// <remarks>
+ /// Task IDs are assigned on-demand and do not necessarily represent the order in the which Task
+ /// instances were created.
+ /// </remarks>
+ public int Id
+ {
+ get
+ {
+ if (m_taskId == 0)
+ {
+ int newId = NewId();
+ Interlocked.CompareExchange(ref m_taskId, newId, 0);
+ }
+
+ return m_taskId;
+ }
+ }
+
+ /// <summary>
+ /// Returns the unique ID of the currently executing <see cref="Task">Task</see>.
+ /// </summary>
+ public static int? CurrentId
+ {
+ get
+ {
+ Task currentTask = InternalCurrent;
+ if (currentTask != null)
+ return currentTask.Id;
+ else
+ return null;
+ }
+ }
+
+ /// <summary>
+ /// Gets the <see cref="Task">Task</see> instance currently executing, or
+ /// null if none exists.
+ /// </summary>
+ internal static Task InternalCurrent
+ {
+ get { return t_currentTask; }
+ }
+
+ /// <summary>
+ /// Gets the Task instance currently executing if the specified creation options
+ /// contain AttachedToParent.
+ /// </summary>
+ /// <param name="options">The options to check.</param>
+ /// <returns>The current task if there is one and if AttachToParent is in the options; otherwise, null.</returns>
+ internal static Task InternalCurrentIfAttached(TaskCreationOptions creationOptions)
+ {
+ return (creationOptions & TaskCreationOptions.AttachedToParent) != 0 ? InternalCurrent : null;
+ }
+
+ /// <summary>
+ /// Gets the StackGuard object assigned to the current thread.
+ /// </summary>
+ internal static StackGuard CurrentStackGuard
+ {
+ get
+ {
+ StackGuard sg = t_stackGuard;
+ if (sg == null)
+ {
+ t_stackGuard = sg = new StackGuard();
+ }
+ return sg;
+ }
+ }
+
+
+ /// <summary>
+ /// Gets the <see cref="T:System.AggregateException">Exception</see> that caused the <see
+ /// cref="Task">Task</see> to end prematurely. If the <see
+ /// cref="Task">Task</see> completed successfully or has not yet thrown any
+ /// exceptions, this will return null.
+ /// </summary>
+ /// <remarks>
+ /// Tasks that throw unhandled exceptions store the resulting exception and propagate it wrapped in a
+ /// <see cref="System.AggregateException"/> in calls to <see cref="Wait()">Wait</see>
+ /// or in accesses to the <see cref="Exception"/> property. Any exceptions not observed by the time
+ /// the Task instance is garbage collected will be propagated on the finalizer thread.
+ /// </remarks>
+ public AggregateException Exception
+ {
+ get
+ {
+ AggregateException e = null;
+
+ // If you're faulted, retrieve the exception(s)
+ if (IsFaulted) e = GetExceptions(false);
+
+ // Only return an exception in faulted state (skip manufactured exceptions)
+ // A "benevolent" race condition makes it possible to return null when IsFaulted is
+ // true (i.e., if IsFaulted is set just after the check to IsFaulted above).
+ Contract.Assert((e == null) || IsFaulted, "Task.Exception_get(): returning non-null value when not Faulted");
+
+ return e;
+ }
+ }
+
+ /// <summary>
+ /// Gets the <see cref="T:System.Threading.Tasks.TaskStatus">TaskStatus</see> of this Task.
+ /// </summary>
+ public TaskStatus Status
+ {
+ get
+ {
+ TaskStatus rval;
+
+ // get a cached copy of the state flags. This should help us
+ // to get a consistent view of the flags if they are changing during the
+ // execution of this method.
+ int sf = m_stateFlags;
+
+ if ((sf & TASK_STATE_FAULTED) != 0) rval = TaskStatus.Faulted;
+ else if ((sf & TASK_STATE_CANCELED) != 0) rval = TaskStatus.Canceled;
+ else if ((sf & TASK_STATE_RAN_TO_COMPLETION) != 0) rval = TaskStatus.RanToCompletion;
+ else if ((sf & TASK_STATE_WAITING_ON_CHILDREN) != 0) rval = TaskStatus.WaitingForChildrenToComplete;
+ else if ((sf & TASK_STATE_DELEGATE_INVOKED) != 0) rval = TaskStatus.Running;
+ else if ((sf & TASK_STATE_STARTED) != 0) rval = TaskStatus.WaitingToRun;
+ else if ((sf & TASK_STATE_WAITINGFORACTIVATION) != 0) rval = TaskStatus.WaitingForActivation;
+ else rval = TaskStatus.Created;
+
+ return rval;
+ }
+ }
+
+ /// <summary>
+ /// Gets whether this <see cref="Task">Task</see> instance has completed
+ /// execution due to being canceled.
+ /// </summary>
+ /// <remarks>
+ /// A <see cref="Task">Task</see> will complete in Canceled state either if its <see cref="CancellationToken">CancellationToken</see>
+ /// was marked for cancellation before the task started executing, or if the task acknowledged the cancellation request on
+ /// its already signaled CancellationToken by throwing an
+ /// <see cref="System.OperationCanceledException">OperationCanceledException</see> that bears the same
+ /// <see cref="System.Threading.CancellationToken">CancellationToken</see>.
+ /// </remarks>
+ public bool IsCanceled
+ {
+ get
+ {
+ // Return true if canceled bit is set and faulted bit is not set
+ return (m_stateFlags & (TASK_STATE_CANCELED | TASK_STATE_FAULTED)) == TASK_STATE_CANCELED;
+ }
+ }
+
+ /// <summary>
+ /// Returns true if this task has a cancellation token and it was signaled.
+ /// To be used internally in execute entry codepaths.
+ /// </summary>
+ internal bool IsCancellationRequested
+ {
+ get
+ {
+ // check both the internal cancellation request flag and the CancellationToken attached to this task
+ var props = Volatile.Read(ref m_contingentProperties);
+ return props != null &&
+ (props.m_internalCancellationRequested == CANCELLATION_REQUESTED ||
+ props.m_cancellationToken.IsCancellationRequested);
+ }
+ }
+
+ /// <summary>
+ /// Ensures that the contingent properties field has been initialized.
+ /// ASSUMES THAT m_stateFlags IS ALREADY SET!
+ /// </summary>
+ /// <returns>The initialized contingent properties object.</returns>
+ internal ContingentProperties EnsureContingentPropertiesInitialized()
+ {
+ return LazyInitializer.EnsureInitialized(ref m_contingentProperties, () => new ContingentProperties());
+ }
+
+ /// <summary>
+ /// Without synchronization, ensures that the contingent properties field has been initialized.
+ /// ASSUMES THAT m_stateFlags IS ALREADY SET!
+ /// </summary>
+ /// <returns>The initialized contingent properties object.</returns>
+ internal ContingentProperties EnsureContingentPropertiesInitializedUnsafe()
+ {
+ return m_contingentProperties ?? (m_contingentProperties = new ContingentProperties());
+ }
+
+ /// <summary>
+ /// This internal property provides access to the CancellationToken that was set on the task
+ /// when it was constructed.
+ /// </summary>
+ internal CancellationToken CancellationToken
+ {
+ get
+ {
+ var props = Volatile.Read(ref m_contingentProperties);
+ return (props == null) ? default(CancellationToken) : props.m_cancellationToken;
+ }
+ }
+
+ /// <summary>
+ /// Gets whether this <see cref="Task"/> threw an OperationCanceledException while its CancellationToken was signaled.
+ /// </summary>
+ internal bool IsCancellationAcknowledged
+ {
+ get { return (m_stateFlags & TASK_STATE_CANCELLATIONACKNOWLEDGED) != 0; }
+ }
+
+
+ /// <summary>
+ /// Gets whether this <see cref="Task">Task</see> has completed.
+ /// </summary>
+ /// <remarks>
+ /// <see cref="IsCompleted"/> will return true when the Task is in one of the three
+ /// final states: <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see>,
+ /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>, or
+ /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see>.
+ /// </remarks>
+ public bool IsCompleted
+ {
+ get
+ {
+ int stateFlags = m_stateFlags; // enable inlining of IsCompletedMethod by "cast"ing away the volatility
+ return IsCompletedMethod(stateFlags);
+ }
+ }
+
+ // Similar to IsCompleted property, but allows for the use of a cached flags value
+ // rather than reading the volatile m_stateFlags field.
+ private static bool IsCompletedMethod(int flags)
+ {
+ return (flags & TASK_STATE_COMPLETED_MASK) != 0;
+ }
+
+ // For use in InternalWait -- marginally faster than (Task.Status == TaskStatus.RanToCompletion)
+ internal bool IsRanToCompletion
+ {
+ get { return (m_stateFlags & TASK_STATE_COMPLETED_MASK) == TASK_STATE_RAN_TO_COMPLETION; }
+ }
+
+ /// <summary>
+ /// Gets the <see cref="T:System.Threading.Tasks.TaskCreationOptions">TaskCreationOptions</see> used
+ /// to create this task.
+ /// </summary>
+ public TaskCreationOptions CreationOptions
+ {
+ get { return Options & (TaskCreationOptions)(~InternalTaskOptions.InternalOptionsMask); }
+ }
+
+ /// <summary>
+ /// Gets a <see cref="T:System.Threading.WaitHandle"/> that can be used to wait for the task to
+ /// complete.
+ /// </summary>
+ /// <remarks>
+ /// Using the wait functionality provided by <see cref="Wait()"/>
+ /// should be preferred over using <see cref="IAsyncResult.AsyncWaitHandle"/> for similar
+ /// functionality.
+ /// </remarks>
+ /// <exception cref="T:System.ObjectDisposedException">
+ /// The <see cref="Task"/> has been disposed.
+ /// </exception>
+ WaitHandle IAsyncResult.AsyncWaitHandle
+ {
+ // Although a slim event is used internally to avoid kernel resource allocation, this function
+ // forces allocation of a true WaitHandle when called.
+ get
+ {
+ bool isDisposed = (m_stateFlags & TASK_STATE_DISPOSED) != 0;
+ if (isDisposed)
+ {
+ ThrowHelper.ThrowObjectDisposedException(ExceptionResource.Task_ThrowIfDisposed);
+ }
+ return CompletedEvent.WaitHandle;
+ }
+ }
+
+ /// <summary>
+ /// Gets the state object supplied when the <see cref="Task">Task</see> was created,
+ /// or null if none was supplied.
+ /// </summary>
+ public object AsyncState
+ {
+ get { return m_stateObject; }
+ }
+
+ /// <summary>
+ /// Gets an indication of whether the asynchronous operation completed synchronously.
+ /// </summary>
+ /// <value>true if the asynchronous operation completed synchronously; otherwise, false.</value>
+ bool IAsyncResult.CompletedSynchronously
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ /// <summary>
+ /// Provides access to the TaskScheduler responsible for executing this Task.
+ /// </summary>
+ internal TaskScheduler ExecutingTaskScheduler
+ {
+ get { return m_taskScheduler; }
+ }
+
+ /// <summary>
+ /// Provides access to factory methods for creating <see cref="Task"/> and <see cref="Task{TResult}"/> instances.
+ /// </summary>
+ /// <remarks>
+ /// The factory returned from <see cref="Factory"/> is a default instance
+ /// of <see cref="System.Threading.Tasks.TaskFactory"/>, as would result from using
+ /// the default constructor on TaskFactory.
+ /// </remarks>
+ public static TaskFactory Factory { get { return s_factory; } }
+
+ /// <summary>A task that's already been completed successfully.</summary>
+ private static Task s_completedTask;
+
+ /// <summary>Gets a task that's already been completed successfully.</summary>
+ /// <remarks>May not always return the same instance.</remarks>
+ public static Task CompletedTask
+ {
+ get
+ {
+ var completedTask = s_completedTask;
+ if (completedTask == null)
+ s_completedTask = completedTask = new Task(false, (TaskCreationOptions)InternalTaskOptions.DoNotDispose, default(CancellationToken)); // benign initialization race condition
+ return completedTask;
+ }
+ }
+
+ /// <summary>
+ /// Provides an event that can be used to wait for completion.
+ /// Only called by IAsyncResult.AsyncWaitHandle, which means that we really do need to instantiate a completion event.
+ /// </summary>
+ internal ManualResetEventSlim CompletedEvent
+ {
+ get
+ {
+ var contingentProps = EnsureContingentPropertiesInitialized();
+ if (contingentProps.m_completionEvent == null)
+ {
+ bool wasCompleted = IsCompleted;
+ ManualResetEventSlim newMre = new ManualResetEventSlim(wasCompleted);
+ if (Interlocked.CompareExchange(ref contingentProps.m_completionEvent, newMre, null) != null)
+ {
+ // Someone else already set the value, so we will just close the event right away.
+ newMre.Dispose();
+ }
+ else if (!wasCompleted && IsCompleted)
+ {
+ // We published the event as unset, but the task has subsequently completed.
+ // Set the event's state properly so that callers don't deadlock.
+ newMre.Set();
+ }
+ }
+
+ return contingentProps.m_completionEvent;
+ }
+ }
+
+ /// <summary>
+ /// Determines whether this is the root task of a self replicating group.
+ /// </summary>
+ internal bool IsSelfReplicatingRoot
+ {
+ get
+ {
+ // Return true if self-replicating bit is set and child replica bit is not set
+ return (Options & (TaskCreationOptions)(InternalTaskOptions.SelfReplicating | InternalTaskOptions.ChildReplica))
+ == (TaskCreationOptions)InternalTaskOptions.SelfReplicating;
+ }
+ }
+
+ /// <summary>
+ /// Determines whether the task is a replica itself.
+ /// </summary>
+ internal bool IsChildReplica
+ {
+ get { return (Options & (TaskCreationOptions)InternalTaskOptions.ChildReplica) != 0; }
+ }
+
+ internal int ActiveChildCount
+ {
+ get
+ {
+ var props = Volatile.Read(ref m_contingentProperties);
+ return props != null ? props.m_completionCountdown - 1 : 0;
+ }
+ }
+
+ /// <summary>
+ /// The property formerly known as IsFaulted.
+ /// </summary>
+ internal bool ExceptionRecorded
+ {
+ get
+ {
+ var props = Volatile.Read(ref m_contingentProperties);
+ return (props != null) && (props.m_exceptionsHolder != null) && (props.m_exceptionsHolder.ContainsFaultList);
+ }
+ }
+
+ /// <summary>
+ /// Gets whether the <see cref="Task"/> completed due to an unhandled exception.
+ /// </summary>
+ /// <remarks>
+ /// If <see cref="IsFaulted"/> is true, the Task's <see cref="Status"/> will be equal to
+ /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">TaskStatus.Faulted</see>, and its
+ /// <see cref="Exception"/> property will be non-null.
+ /// </remarks>
+ public bool IsFaulted
+ {
+ get
+ {
+ // Faulted is "king" -- if that bit is present (regardless of other bits), we are faulted.
+ return ((m_stateFlags & TASK_STATE_FAULTED) != 0);
+ }
+ }
+
+ /// <summary>
+ /// The captured execution context for the current task to run inside
+ /// If the TASK_STATE_EXECUTIONCONTEXT_IS_NULL flag is set, this means ExecutionContext.Capture returned null, otherwise
+ /// If the captured context is the default, nothing is saved, otherwise the m_contingentProperties inflates to save the context
+ /// </summary>
+ internal ExecutionContext CapturedContext
+ {
+ get
+ {
+ if ((m_stateFlags & TASK_STATE_EXECUTIONCONTEXT_IS_NULL) == TASK_STATE_EXECUTIONCONTEXT_IS_NULL)
+ {
+ return null;
+ }
+ else
+ {
+ return m_contingentProperties?.m_capturedContext ?? ExecutionContext.PreAllocatedDefault;
+ }
+ }
+ set
+ {
+ // 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
+ if (value == null)
+ {
+ m_stateFlags |= TASK_STATE_EXECUTIONCONTEXT_IS_NULL;
+ }
+ else if (!value.IsPreAllocatedDefault) // not the default context, then inflate the contingent properties and set it
+ {
+ EnsureContingentPropertiesInitializedUnsafe().m_capturedContext = value;
+ }
+ //else do nothing, this is the default context
+ }
+ }
+
+ /// <summary>
+ /// Static helper function to copy specific ExecutionContext
+ /// </summary>
+ /// <param name="capturedContext">The captured context</param>
+ /// <returns>The copied context, null if the capturedContext is null</returns>
+ private static ExecutionContext CopyExecutionContext(ExecutionContext capturedContext)
+ {
+ if (capturedContext == null)
+ return null;
+ if (capturedContext.IsPreAllocatedDefault)
+ return ExecutionContext.PreAllocatedDefault;
+
+ return capturedContext.CreateCopy();
+ }
+
+
+#if DEBUG
+ /// <summary>
+ /// Retrieves an identifier for the task.
+ /// </summary>
+ internal int InternalId
+ {
+ get { return GetHashCode(); }
+ }
+#endif
+
+ /////////////
+ // methods
+
+
+ /// <summary>
+ /// Disposes the <see cref="Task"/>, releasing all of its unmanaged resources.
+ /// </summary>
+ /// <remarks>
+ /// Unlike most of the members of <see cref="Task"/>, this method is not thread-safe.
+ /// Also, <see cref="Dispose()"/> may only be called on a <see cref="Task"/> that is in one of
+ /// the final states: <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see>,
+ /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>, or
+ /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see>.
+ /// </remarks>
+ /// <exception cref="T:System.InvalidOperationException">
+ /// The exception that is thrown if the <see cref="Task"/> is not in
+ /// one of the final states: <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see>,
+ /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>, or
+ /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see>.
+ /// </exception>
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ /// <summary>
+ /// Disposes the <see cref="Task"/>, releasing all of its unmanaged resources.
+ /// </summary>
+ /// <param name="disposing">
+ /// A Boolean value that indicates whether this method is being called due to a call to <see
+ /// cref="Dispose()"/>.
+ /// </param>
+ /// <remarks>
+ /// Unlike most of the members of <see cref="Task"/>, this method is not thread-safe.
+ /// </remarks>
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ // Dispose is a nop if this task was created with the DoNotDispose internal option.
+ // This is done before the completed check, because if we're not touching any
+ // state on the task, it's ok for it to happen before completion.
+ if ((Options & (TaskCreationOptions)InternalTaskOptions.DoNotDispose) != 0)
+ {
+ return;
+ }
+
+ // Task must be completed to dispose
+ if (!IsCompleted)
+ {
+ ThrowHelper.ThrowInvalidOperationException(ExceptionResource.Task_Dispose_NotCompleted);
+ }
+
+ // Dispose of the underlying completion event if it exists
+ var cp = Volatile.Read(ref m_contingentProperties);
+ if (cp != null)
+ {
+ // Make a copy to protect against racing Disposes.
+ // If we wanted to make this a bit safer, we could use an interlocked here,
+ // but we state that Dispose is not thread safe.
+ var ev = cp.m_completionEvent;
+ if (ev != null)
+ {
+ // Null out the completion event in contingent props; we'll use our copy from here on out
+ cp.m_completionEvent = null;
+
+ // In the unlikely event that our completion event is inflated but not yet signaled,
+ // go ahead and signal the event. If you dispose of an unsignaled MRES, then any waiters
+ // will deadlock; an ensuing Set() will not wake them up. In the event of an AppDomainUnload,
+ // there is no guarantee that anyone else is going to signal the event, and it does no harm to
+ // call Set() twice on m_completionEvent.
+ if (!ev.IsSet) ev.Set();
+
+ // Finally, dispose of the event
+ ev.Dispose();
+ }
+ }
+ }
+
+ // We OR the flags to indicate the object has been disposed. The task
+ // has already completed at this point, and the only conceivable race condition would
+ // be with the unsetting of the TASK_STATE_WAIT_COMPLETION_NOTIFICATION flag, which
+ // is extremely unlikely and also benign. (Worst case: we hit a breakpoint
+ // twice instead of once in the debugger. Weird, but not lethal.)
+ m_stateFlags |= TASK_STATE_DISPOSED;
+ }
+
+ /////////////
+ // internal helpers
+
+
+ /// <summary>
+ /// Schedules the task for execution.
+ /// </summary>
+ /// <param name="needsProtection">If true, TASK_STATE_STARTED bit is turned on in
+ /// an atomic fashion, making sure that TASK_STATE_CANCELED does not get set
+ /// underneath us. If false, TASK_STATE_STARTED bit is OR-ed right in. This
+ /// allows us to streamline things a bit for StartNew(), where competing cancellations
+ /// are not a problem.</param>
+ [SecuritySafeCritical] // Needed for QueueTask
+ internal void ScheduleAndStart(bool needsProtection)
+ {
+ Contract.Assert(m_taskScheduler != null, "expected a task scheduler to have been selected");
+ Contract.Assert((m_stateFlags & TASK_STATE_STARTED) == 0, "task has already started");
+
+ // Set the TASK_STATE_STARTED bit
+ if (needsProtection)
+ {
+ if (!MarkStarted())
+ {
+ // A cancel has snuck in before we could get started. Quietly exit.
+ return;
+ }
+ }
+ else
+ {
+ m_stateFlags |= TASK_STATE_STARTED;
+ }
+
+ if (s_asyncDebuggingEnabled)
+ {
+ AddToActiveTasks(this);
+ }
+
+ if (AsyncCausalityTracer.LoggingOn && (Options & (TaskCreationOptions)InternalTaskOptions.ContinuationTask) == 0)
+ {
+ //For all other task than TaskContinuations we want to log. TaskContinuations log in their constructor
+ AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, this.Id, "Task: "+((Delegate)m_action).Method.Name, 0);
+ }
+
+
+ try
+ {
+ // Queue to the indicated scheduler.
+ m_taskScheduler.InternalQueueTask(this);
+ }
+ catch (ThreadAbortException tae)
+ {
+ AddException(tae);
+ FinishThreadAbortedTask(true, false);
+ }
+ catch (Exception e)
+ {
+ // The scheduler had a problem queueing this task. Record the exception, leaving this task in
+ // a Faulted state.
+ TaskSchedulerException tse = new TaskSchedulerException(e);
+ AddException(tse);
+ Finish(false);
+
+ // Now we need to mark ourselves as "handled" to avoid crashing the finalizer thread if we are called from StartNew()
+ // or from the self replicating logic, because in both cases the exception is either propagated outside directly, or added
+ // to an enclosing parent. However we won't do this for continuation tasks, because in that case we internally eat the exception
+ // and therefore we need to make sure the user does later observe it explicitly or see it on the finalizer.
+
+ if ((Options & (TaskCreationOptions)InternalTaskOptions.ContinuationTask) == 0)
+ {
+ // m_contingentProperties.m_exceptionsHolder *should* already exist after AddException()
+ Contract.Assert(
+ (m_contingentProperties != null) &&
+ (m_contingentProperties.m_exceptionsHolder != null) &&
+ (m_contingentProperties.m_exceptionsHolder.ContainsFaultList),
+ "Task.ScheduleAndStart(): Expected m_contingentProperties.m_exceptionsHolder to exist " +
+ "and to have faults recorded.");
+
+ m_contingentProperties.m_exceptionsHolder.MarkAsHandled(false);
+ }
+ // re-throw the exception wrapped as a TaskSchedulerException.
+ throw tse;
+ }
+ }
+
+ /// <summary>
+ /// Adds an exception to the list of exceptions this task has thrown.
+ /// </summary>
+ /// <param name="exceptionObject">An object representing either an Exception or a collection of Exceptions.</param>
+ internal void AddException(object exceptionObject)
+ {
+ Contract.Requires(exceptionObject != null, "Task.AddException: Expected a non-null exception object");
+ AddException(exceptionObject, representsCancellation: false);
+ }
+
+ /// <summary>
+ /// Adds an exception to the list of exceptions this task has thrown.
+ /// </summary>
+ /// <param name="exceptionObject">An object representing either an Exception or a collection of Exceptions.</param>
+ /// <param name="representsCancellation">Whether the exceptionObject is an OperationCanceledException representing cancellation.</param>
+ internal void AddException(object exceptionObject, bool representsCancellation)
+ {
+ Contract.Requires(exceptionObject != null, "Task.AddException: Expected a non-null exception object");
+
+#if DEBUG
+ var eoAsException = exceptionObject as Exception;
+ var eoAsEnumerableException = exceptionObject as IEnumerable<Exception>;
+ var eoAsEdi = exceptionObject as ExceptionDispatchInfo;
+ var eoAsEnumerableEdi = exceptionObject as IEnumerable<ExceptionDispatchInfo>;
+
+ Contract.Assert(
+ eoAsException != null || eoAsEnumerableException != null || eoAsEdi != null || eoAsEnumerableEdi != null,
+ "Task.AddException: Expected an Exception, ExceptionDispatchInfo, or an IEnumerable<> of one of those");
+
+ var eoAsOce = exceptionObject as OperationCanceledException;
+
+ Contract.Assert(
+ !representsCancellation ||
+ eoAsOce != null ||
+ (eoAsEdi != null && eoAsEdi.SourceException is OperationCanceledException),
+ "representsCancellation should be true only if an OCE was provided.");
+#endif
+
+ //
+ // WARNING: A great deal of care went into ensuring that
+ // AddException() and GetExceptions() are never called
+ // simultaneously. See comment at start of GetExceptions().
+ //
+
+ // Lazily initialize the holder, ensuring only one thread wins.
+ var props = EnsureContingentPropertiesInitialized();
+ if (props.m_exceptionsHolder == null)
+ {
+ TaskExceptionHolder holder = new TaskExceptionHolder(this);
+ if (Interlocked.CompareExchange(ref props.m_exceptionsHolder, holder, null) != null)
+ {
+ // If someone else already set the value, suppress finalization.
+ holder.MarkAsHandled(false);
+ }
+ }
+
+ lock (props)
+ {
+ props.m_exceptionsHolder.Add(exceptionObject, representsCancellation);
+ }
+ }
+
+ /// <summary>
+ /// Returns a list of exceptions by aggregating the holder's contents. Or null if
+ /// no exceptions have been thrown.
+ /// </summary>
+ /// <param name="includeTaskCanceledExceptions">Whether to include a TCE if cancelled.</param>
+ /// <returns>An aggregate exception, or null if no exceptions have been caught.</returns>
+ private AggregateException GetExceptions(bool includeTaskCanceledExceptions)
+ {
+ //
+ // WARNING: The Task/Task<TResult>/TaskCompletionSource classes
+ // have all been carefully crafted to insure that GetExceptions()
+ // is never called while AddException() is being called. There
+ // are locks taken on m_contingentProperties in several places:
+ //
+ // -- Task<TResult>.TrySetException(): The lock allows the
+ // task to be set to Faulted state, and all exceptions to
+ // be recorded, in one atomic action.
+ //
+ // -- Task.Exception_get(): The lock ensures that Task<TResult>.TrySetException()
+ // is allowed to complete its operation before Task.Exception_get()
+ // can access GetExceptions().
+ //
+ // -- Task.ThrowIfExceptional(): The lock insures that Wait() will
+ // not attempt to call GetExceptions() while Task<TResult>.TrySetException()
+ // is in the process of calling AddException().
+ //
+ // For "regular" tasks, we effectively keep AddException() and GetException()
+ // from being called concurrently by the way that the state flows. Until
+ // a Task is marked Faulted, Task.Exception_get() returns null. And
+ // a Task is not marked Faulted until it and all of its children have
+ // completed, which means that all exceptions have been recorded.
+ //
+ // It might be a lot easier to follow all of this if we just required
+ // that all calls to GetExceptions() and AddExceptions() were made
+ // under a lock on m_contingentProperties. But that would also
+ // increase our lock occupancy time and the frequency with which we
+ // would need to take the lock.
+ //
+ // If you add a call to GetExceptions() anywhere in the code,
+ // please continue to maintain the invariant that it can't be
+ // called when AddException() is being called.
+ //
+
+ // We'll lazily create a TCE if the task has been canceled.
+ Exception canceledException = null;
+ if (includeTaskCanceledExceptions && IsCanceled)
+ {
+ // Backcompat:
+ // Ideally we'd just use the cached OCE from this.GetCancellationExceptionDispatchInfo()
+ // here. However, that would result in a potentially breaking change from .NET 4, which
+ // has the code here that throws a new exception instead of the original, and the EDI
+ // may not contain a TCE, but an OCE or any OCE-derived type, which would mean we'd be
+ // propagating an exception of a different type.
+ canceledException = new TaskCanceledException(this);
+ }
+
+ if (ExceptionRecorded)
+ {
+ // There are exceptions; get the aggregate and optionally add the canceled
+ // exception to the aggregate (if applicable).
+ Contract.Assert(m_contingentProperties != null); // ExceptionRecorded ==> m_contingentProperties != null
+
+ // No need to lock around this, as other logic prevents the consumption of exceptions
+ // before they have been completely processed.
+ return m_contingentProperties.m_exceptionsHolder.CreateExceptionObject(false, canceledException);
+ }
+ else if (canceledException != null)
+ {
+ // No exceptions, but there was a cancelation. Aggregate and return it.
+ return new AggregateException(canceledException);
+ }
+
+ return null;
+ }
+
+ /// <summary>Gets the exception dispatch infos once the task has faulted.</summary>
+ internal ReadOnlyCollection<ExceptionDispatchInfo> GetExceptionDispatchInfos()
+ {
+ bool exceptionsAvailable = IsFaulted && ExceptionRecorded;
+ Contract.Assert(exceptionsAvailable, "Must only be used when the task has faulted with exceptions.");
+ return exceptionsAvailable ?
+ m_contingentProperties.m_exceptionsHolder.GetExceptionDispatchInfos() :
+ new ReadOnlyCollection<ExceptionDispatchInfo>(new ExceptionDispatchInfo[0]);
+ }
+
+ /// <summary>Gets the ExceptionDispatchInfo containing the OperationCanceledException for this task.</summary>
+ /// <returns>The ExceptionDispatchInfo. May be null if no OCE was stored for the task.</returns>
+ internal ExceptionDispatchInfo GetCancellationExceptionDispatchInfo()
+ {
+ Contract.Assert(IsCanceled, "Must only be used when the task has canceled.");
+ return Volatile.Read(ref m_contingentProperties)?.m_exceptionsHolder?.GetCancellationExceptionDispatchInfo(); // may be null
+ }
+
+ /// <summary>
+ /// Throws an aggregate exception if the task contains exceptions.
+ /// </summary>
+ internal void ThrowIfExceptional(bool includeTaskCanceledExceptions)
+ {
+ Contract.Requires(IsCompleted, "ThrowIfExceptional(): Expected IsCompleted == true");
+
+ Exception exception = GetExceptions(includeTaskCanceledExceptions);
+ if (exception != null)
+ {
+ UpdateExceptionObservedStatus();
+ throw exception;
+ }
+ }
+
+ /// <summary>
+ /// Checks whether this is an attached task, and whether we are being called by the parent task.
+ /// And sets the TASK_STATE_EXCEPTIONOBSERVEDBYPARENT status flag based on that.
+ ///
+ /// This is meant to be used internally when throwing an exception, and when WaitAll is gathering
+ /// exceptions for tasks it waited on. If this flag gets set, the implicit wait on children
+ /// will skip exceptions to prevent duplication.
+ ///
+ /// This should only be called when this task has completed with an exception
+ ///
+ /// </summary>
+ internal void UpdateExceptionObservedStatus()
+ {
+ Task parent = m_contingentProperties?.m_parent;
+ if ((parent != null)
+ && ((Options & TaskCreationOptions.AttachedToParent) != 0)
+ && ((parent.CreationOptions & TaskCreationOptions.DenyChildAttach) == 0)
+ && Task.InternalCurrent == parent)
+ {
+ m_stateFlags |= TASK_STATE_EXCEPTIONOBSERVEDBYPARENT;
+ }
+ }
+
+ /// <summary>
+ /// Checks whether the TASK_STATE_EXCEPTIONOBSERVEDBYPARENT status flag is set,
+ /// This will only be used by the implicit wait to prevent double throws
+ ///
+ /// </summary>
+ internal bool IsExceptionObservedByParent
+ {
+ get
+ {
+ return (m_stateFlags & TASK_STATE_EXCEPTIONOBSERVEDBYPARENT) != 0;
+ }
+ }
+
+ /// <summary>
+ /// Checks whether the body was ever invoked. Used by task scheduler code to verify custom schedulers actually ran the task.
+ /// </summary>
+ internal bool IsDelegateInvoked
+ {
+ get
+ {
+ return (m_stateFlags & TASK_STATE_DELEGATE_INVOKED) != 0;
+ }
+ }
+
+ /// <summary>
+ /// Signals completion of this particular task.
+ ///
+ /// The bUserDelegateExecuted parameter indicates whether this Finish() call comes following the
+ /// full execution of the user delegate.
+ ///
+ /// If bUserDelegateExecuted is false, it mean user delegate wasn't invoked at all (either due to
+ /// a cancellation request, or because this task is a promise style Task). In this case, the steps
+ /// involving child tasks (i.e. WaitForChildren) will be skipped.
+ ///
+ /// </summary>
+ internal void Finish(bool bUserDelegateExecuted)
+ {
+ if (!bUserDelegateExecuted)
+ {
+ // delegate didn't execute => no children. We can safely call the remaining finish stages
+ FinishStageTwo();
+ }
+ else
+ {
+ var props = Volatile.Read(ref m_contingentProperties);
+
+ if (props == null || // no contingent properties means no children, so it's safe to complete ourselves
+ (props.m_completionCountdown == 1 && !IsSelfReplicatingRoot) ||
+ // Count of 1 => either all children finished, or there were none. Safe to complete ourselves
+ // without paying the price of an Interlocked.Decrement.
+ // However we need to exclude self replicating root tasks from this optimization, because
+ // they can have children joining in, or finishing even after the root task delegate is done.
+ Interlocked.Decrement(ref props.m_completionCountdown) == 0) // Reaching this sub clause means there may be remaining active children,
+ // and we could be racing with one of them to call FinishStageTwo().
+ // So whoever does the final Interlocked.Dec is responsible to finish.
+ {
+ FinishStageTwo();
+ }
+ else
+ {
+ // Apparently some children still remain. It will be up to the last one to process the completion of this task on their own thread.
+ // We will now yield the thread back to ThreadPool. Mark our state appropriately before getting out.
+
+ // We have to use an atomic update for this and make sure not to overwrite a final state,
+ // because at this very moment the last child's thread may be concurrently completing us.
+ // Otherwise we risk overwriting the TASK_STATE_RAN_TO_COMPLETION, _CANCELED or _FAULTED bit which may have been set by that child task.
+ // Note that the concurrent update by the last child happening in FinishStageTwo could still wipe out the TASK_STATE_WAITING_ON_CHILDREN flag,
+ // but it is not critical to maintain, therefore we dont' need to intruduce a full atomic update into FinishStageTwo
+
+ AtomicStateUpdate(TASK_STATE_WAITING_ON_CHILDREN, TASK_STATE_FAULTED | TASK_STATE_CANCELED | TASK_STATE_RAN_TO_COMPLETION);
+ }
+
+ // 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.
+ // we use a local variable for exceptional children here because some other thread may be nulling out m_contingentProperties.m_exceptionalChildren
+ List<Task> exceptionalChildren = props != null ? props.m_exceptionalChildren : null;
+
+ if (exceptionalChildren != null)
+ {
+ lock (exceptionalChildren)
+ {
+ exceptionalChildren.RemoveAll(s_IsExceptionObservedByParentPredicate); // RemoveAll has better performance than doing it ourselves
+ }
+ }
+ }
+ }
+
+ // statically allocated delegate for the removeall expression in Finish()
+ private readonly static Predicate<Task> s_IsExceptionObservedByParentPredicate = new Predicate<Task>((t) => { return t.IsExceptionObservedByParent; });
+
+ /// <summary>
+ /// FinishStageTwo is to be executed as soon as we known there are no more children to complete.
+ /// 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)
+ /// ii) or on the thread that executed the last child.
+ /// </summary>
+ internal void FinishStageTwo()
+ {
+ AddExceptionsFromChildren();
+
+ // At this point, the task is done executing and waiting for its children,
+ // we can transition our task to a completion state.
+ int completionState;
+ if (ExceptionRecorded)
+ {
+ completionState = TASK_STATE_FAULTED;
+ if (AsyncCausalityTracer.LoggingOn)
+ AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, this.Id, AsyncCausalityStatus.Error);
+
+ if (Task.s_asyncDebuggingEnabled)
+ {
+ RemoveFromActiveTasks(this.Id);
+ }
+ }
+ else if (IsCancellationRequested && IsCancellationAcknowledged)
+ {
+ // We transition into the TASK_STATE_CANCELED final state if the task's CT was signalled for cancellation,
+ // and the user delegate acknowledged the cancellation request by throwing an OCE,
+ // and the task hasn't otherwise transitioned into faulted state. (TASK_STATE_FAULTED trumps TASK_STATE_CANCELED)
+ //
+ // If the task threw an OCE without cancellation being requestsed (while the CT not being in signaled state),
+ // then we regard it as a regular exception
+
+ completionState = TASK_STATE_CANCELED;
+ if (AsyncCausalityTracer.LoggingOn)
+ AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, this.Id, AsyncCausalityStatus.Canceled);
+
+ if (Task.s_asyncDebuggingEnabled)
+ {
+ RemoveFromActiveTasks(this.Id);
+ }
+ }
+ else
+ {
+ completionState = TASK_STATE_RAN_TO_COMPLETION;
+ if (AsyncCausalityTracer.LoggingOn)
+ AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, this.Id, AsyncCausalityStatus.Completed);
+
+ if (Task.s_asyncDebuggingEnabled)
+ {
+ RemoveFromActiveTasks(this.Id);
+ }
+ }
+
+ // Use Interlocked.Exchange() to effect a memory fence, preventing
+ // any SetCompleted() (or later) instructions from sneak back before it.
+ Interlocked.Exchange(ref m_stateFlags, m_stateFlags | completionState);
+
+ // Set the completion event if it's been lazy allocated.
+ // And if we made a cancellation registration, it's now unnecessary.
+ var cp = Volatile.Read(ref m_contingentProperties);
+ if (cp != null)
+ {
+ cp.SetCompleted();
+ cp.DeregisterCancellationCallback();
+ }
+
+ // ready to run continuations and notify parent.
+ FinishStageThree();
+ }
+
+
+ /// <summary>
+ /// Final stage of the task completion code path. Notifies the parent (if any) that another of its children are done, and runs continuations.
+ /// This function is only separated out from FinishStageTwo because these two operations are also needed to be called from CancellationCleanupLogic()
+ /// </summary>
+ internal void FinishStageThree()
+ {
+ // Release the action so that holding this task object alive doesn't also
+ // hold alive the body of the task. We do this before notifying a parent,
+ // so that if notifying the parent completes the parent and causes
+ // its synchronous continuations to run, the GC can collect the state
+ // in the interim. And we do it before finishing continuations, because
+ // continuations hold onto the task, and therefore are keeping it alive.
+ m_action = null;
+
+ // Notify parent if this was an attached task
+ Task parent = m_contingentProperties?.m_parent;
+ if (parent != null
+ && ((parent.CreationOptions & TaskCreationOptions.DenyChildAttach) == 0)
+ && (((TaskCreationOptions)(m_stateFlags & OptionsMask)) & TaskCreationOptions.AttachedToParent) != 0)
+ {
+ parent.ProcessChildCompletion(this);
+ }
+
+ // Activate continuations (if any).
+ FinishContinuations();
+ }
+
+ /// <summary>
+ /// This is called by children of this task when they are completed.
+ /// </summary>
+ internal void ProcessChildCompletion(Task childTask)
+ {
+ Contract.Requires(childTask != null);
+ Contract.Requires(childTask.IsCompleted, "ProcessChildCompletion was called for an uncompleted task");
+
+ Contract.Assert(childTask.m_contingentProperties?.m_parent == this, "ProcessChildCompletion should only be called for a child of this task");
+
+ var props = Volatile.Read(ref m_contingentProperties);
+
+ // if the child threw and we haven't observed it we need to save it for future reference
+ if (childTask.IsFaulted && !childTask.IsExceptionObservedByParent)
+ {
+ // Lazily initialize the child exception list
+ if (props.m_exceptionalChildren == null)
+ {
+ Interlocked.CompareExchange(ref props.m_exceptionalChildren, new List<Task>(), null);
+ }
+
+ // In rare situations involving AppDomainUnload, it's possible (though unlikely) for FinishStageTwo() to be called
+ // multiple times for the same task. In that case, AddExceptionsFromChildren() could be nulling m_exceptionalChildren
+ // out at the same time that we're processing it, resulting in a NullReferenceException here. We'll protect
+ // ourselves by caching m_exceptionChildren in a local variable.
+ List<Task> tmp = props.m_exceptionalChildren;
+ if (tmp != null)
+ {
+ lock (tmp)
+ {
+ tmp.Add(childTask);
+ }
+ }
+
+ }
+
+ if (Interlocked.Decrement(ref props.m_completionCountdown) == 0)
+ {
+ // This call came from the final child to complete, and apparently we have previously given up this task's right to complete itself.
+ // So we need to invoke the final finish stage.
+
+ FinishStageTwo();
+ }
+ }
+
+ /// <summary>
+ /// This is to be called just before the task does its final state transition.
+ /// It traverses the list of exceptional children, and appends their aggregate exceptions into this one's exception list
+ /// </summary>
+ internal void AddExceptionsFromChildren()
+ {
+ // In rare occurences during AppDomainUnload() processing, it is possible for this method to be called
+ // simultaneously on the same task from two different contexts. This can result in m_exceptionalChildren
+ // being nulled out while it is being processed, which could lead to a NullReferenceException. To
+ // protect ourselves, we'll cache m_exceptionalChildren in a local variable.
+ var props = Volatile.Read(ref m_contingentProperties);
+ List<Task> exceptionalChildren = props?.m_exceptionalChildren;
+
+ if (exceptionalChildren != null)
+ {
+ // This lock is necessary because even though AddExceptionsFromChildren is last to execute, it may still
+ // be racing with the code segment at the bottom of Finish() that prunes the exceptional child array.
+ lock (exceptionalChildren)
+ {
+ foreach (Task task in exceptionalChildren)
+ {
+ // Ensure any exceptions thrown by children are added to the parent.
+ // In doing this, we are implicitly marking children as being "handled".
+ Contract.Assert(task.IsCompleted, "Expected all tasks in list to be completed");
+ if (task.IsFaulted && !task.IsExceptionObservedByParent)
+ {
+ TaskExceptionHolder exceptionHolder = Volatile.Read(ref task.m_contingentProperties).m_exceptionsHolder;
+ Contract.Assert(exceptionHolder != null);
+
+ // No locking necessary since child task is finished adding exceptions
+ // and concurrent CreateExceptionObject() calls do not constitute
+ // a concurrency hazard.
+ AddException(exceptionHolder.CreateExceptionObject(false, null));
+ }
+ }
+ }
+
+ // Reduce memory pressure by getting rid of the array
+ props.m_exceptionalChildren = null;
+ }
+ }
+
+ /// <summary>
+ /// Special purpose Finish() entry point to be used when the task delegate throws a ThreadAbortedException
+ /// This makes a note in the state flags so that we avoid any costly synchronous operations in the finish codepath
+ /// such as inlined continuations
+ /// </summary>
+ /// <param name="bTAEAddedToExceptionHolder">
+ /// Indicates whether the ThreadAbortException was added to this task's exception holder.
+ /// This should always be true except for the case of non-root self replicating task copies.
+ /// </param>
+ /// <param name="delegateRan">Whether the delegate was executed.</param>
+ internal void FinishThreadAbortedTask(bool bTAEAddedToExceptionHolder, bool delegateRan)
+ {
+ Contract.Assert(!bTAEAddedToExceptionHolder || m_contingentProperties?.m_exceptionsHolder != null,
+ "FinishThreadAbortedTask() called on a task whose exception holder wasn't initialized");
+
+ // this will only be false for non-root self replicating task copies, because all of their exceptions go to the root task.
+ if (bTAEAddedToExceptionHolder)
+ m_contingentProperties.m_exceptionsHolder.MarkAsHandled(false);
+
+ // If this method has already been called for this task, or if this task has already completed, then
+ // return before actually calling Finish().
+ if (!AtomicStateUpdate(TASK_STATE_THREAD_WAS_ABORTED,
+ TASK_STATE_THREAD_WAS_ABORTED | TASK_STATE_RAN_TO_COMPLETION | TASK_STATE_FAULTED | TASK_STATE_CANCELED))
+ {
+ return;
+ }
+
+ Finish(delegateRan);
+
+ }
+
+
+ /// <summary>
+ /// Executes the task. This method will only be called once, and handles bookeeping associated with
+ /// self-replicating tasks, in addition to performing necessary exception marshaling.
+ /// </summary>
+ private void Execute()
+ {
+ if (IsSelfReplicatingRoot)
+ {
+ ExecuteSelfReplicating(this);
+ }
+ else
+ {
+ try
+ {
+ InnerInvoke();
+ }
+ catch (ThreadAbortException tae)
+ {
+ // Don't record the TAE or call FinishThreadAbortedTask for a child replica task --
+ // it's already been done downstream.
+ if (!IsChildReplica)
+ {
+ // Record this exception in the task's exception list
+ HandleException(tae);
+
+ // This is a ThreadAbortException and it will be rethrown from this catch clause, causing us to
+ // skip the regular Finish codepath. In order not to leave the task unfinished, we now call
+ // FinishThreadAbortedTask here.
+ FinishThreadAbortedTask(true, true);
+ }
+ }
+ catch (Exception exn)
+ {
+ // Record this exception in the task's exception list
+ HandleException(exn);
+ }
+ }
+ }
+
+ // Allows (internal) deriving classes to support limited replication.
+ // (By default, replication is basically unlimited).
+ internal virtual bool ShouldReplicate()
+ {
+ return true;
+ }
+
+ // Allows (internal) deriving classes to instantiate the task replica as a Task super class of their choice
+ // (By default, we create a regular Task instance)
+ internal virtual Task CreateReplicaTask(Action<object> taskReplicaDelegate, Object stateObject, Task parentTask, TaskScheduler taskScheduler,
+ TaskCreationOptions creationOptionsForReplica, InternalTaskOptions internalOptionsForReplica)
+ {
+ return new Task(taskReplicaDelegate, stateObject, parentTask, default(CancellationToken),
+ creationOptionsForReplica, internalOptionsForReplica, parentTask.ExecutingTaskScheduler);
+ }
+
+ // Allows internal deriving classes to support replicas that exit prematurely and want to pass on state to the next replica
+ internal virtual Object SavedStateForNextReplica
+ {
+ get { return null; }
+
+ set { /*do nothing*/ }
+ }
+
+ // Allows internal deriving classes to support replicas that exit prematurely and want to pass on state to the next replica
+ internal virtual Object SavedStateFromPreviousReplica
+ {
+ get { return null; }
+
+ set { /*do nothing*/ }
+ }
+
+ // Allows internal deriving classes to support replicas that exit prematurely and want to hand over the child replica that they
+ // had queued, so that the replacement replica can work with that child task instead of queuing up yet another one
+ internal virtual Task HandedOverChildReplica
+ {
+ get { return null; }
+
+ set { /* do nothing*/ }
+ }
+
+ private static void ExecuteSelfReplicating(Task root)
+ {
+ TaskCreationOptions creationOptionsForReplicas = root.CreationOptions | TaskCreationOptions.AttachedToParent;
+ InternalTaskOptions internalOptionsForReplicas =
+ InternalTaskOptions.ChildReplica | // child replica flag disables self replication for the replicas themselves.
+ InternalTaskOptions.SelfReplicating | // we still want to identify this as part of a self replicating group
+ InternalTaskOptions.QueuedByRuntime; // we queue and cancel these tasks internally, so don't allow CT registration to take place
+
+
+ // Important Note: The child replicas we launch from here will be attached the root replica (by virtue of the root.CreateReplicaTask call)
+ // because we need the root task to receive all their exceptions, and to block until all of them return
+
+
+ // This variable is captured in a closure and shared among all replicas.
+ bool replicasAreQuitting = false;
+
+ // Set up a delegate that will form the body of the root and all recursively created replicas.
+ Action<object> taskReplicaDelegate = null;
+ taskReplicaDelegate = delegate
+ {
+ Task currentTask = Task.InternalCurrent;
+
+
+ // Check if a child task has been handed over by a prematurely quiting replica that we might be a replacement for.
+ Task childTask = currentTask.HandedOverChildReplica;
+
+ if (childTask == null)
+ {
+ // Apparently we are not a replacement task. This means we need to queue up a child task for replication to progress
+
+ // Down-counts a counter in the root task.
+ if (!root.ShouldReplicate()) return;
+
+ // If any of the replicas have quit, we will do so ourselves.
+ if (Volatile.Read(ref replicasAreQuitting))
+ {
+ return;
+ }
+
+ // Propagate a copy of the context from the root task. It may be null if flow was suppressed.
+ ExecutionContext creatorContext = root.CapturedContext;
+
+
+ childTask = root.CreateReplicaTask(taskReplicaDelegate, root.m_stateObject, root, root.ExecutingTaskScheduler,
+ creationOptionsForReplicas, internalOptionsForReplicas);
+
+ childTask.CapturedContext = CopyExecutionContext(creatorContext);
+
+ childTask.ScheduleAndStart(false);
+ }
+
+
+
+ // Finally invoke the meat of the task.
+ // Note that we are directly calling root.InnerInvoke() even though we are currently be in the action delegate of a child replica
+ // This is because the actual work was passed down in that delegate, and the action delegate of the child replica simply contains this
+ // replication control logic.
+ try
+ {
+ // passing in currentTask only so that the parallel debugger can find it
+ root.InnerInvokeWithArg(currentTask);
+ }
+ catch (Exception exn)
+ {
+ // Record this exception in the root task's exception list
+ root.HandleException(exn);
+
+ if (exn is ThreadAbortException)
+ {
+ // If this is a ThreadAbortException it will escape this catch clause, causing us to skip the regular Finish codepath
+ // In order not to leave the task unfinished, we now call FinishThreadAbortedTask here
+ currentTask.FinishThreadAbortedTask(false, true);
+ }
+ }
+
+ Object savedState = currentTask.SavedStateForNextReplica;
+
+ // check for premature exit
+ if (savedState != null)
+ {
+ // the replica decided to exit early
+ // we need to queue up a replacement, attach the saved state, and yield the thread right away
+
+ Task replacementReplica = root.CreateReplicaTask(taskReplicaDelegate, root.m_stateObject, root, root.ExecutingTaskScheduler,
+ creationOptionsForReplicas, internalOptionsForReplicas);
+
+ // Propagate a copy of the context from the root task to the replacement task
+ ExecutionContext creatorContext = root.CapturedContext;
+ replacementReplica.CapturedContext = CopyExecutionContext(creatorContext);
+
+ replacementReplica.HandedOverChildReplica = childTask;
+ replacementReplica.SavedStateFromPreviousReplica = savedState;
+
+ replacementReplica.ScheduleAndStart(false);
+ }
+ else
+ {
+ // The replica finished normally, which means it can't find more work to grab.
+ // Time to mark replicas quitting
+
+ replicasAreQuitting = true;
+
+ // InternalCancel() could conceivably throw in the underlying scheduler's TryDequeue() method.
+ // If it does, then make sure that we record it.
+ try
+ {
+ childTask.InternalCancel(true);
+ }
+ catch (Exception e)
+ {
+ // Apparently TryDequeue threw an exception. Before propagating that exception, InternalCancel should have
+ // attempted an atomic state transition and a call to CancellationCleanupLogic() on this task. So we know
+ // the task was properly cleaned up if it was possible.
+ //
+ // Now all we need to do is to Record the exception in the root task.
+
+ root.HandleException(e);
+ }
+
+ // No specific action needed if the child could not be canceled
+ // because we attached it to the root task, which should therefore be receiving any exceptions from the child,
+ // and root.wait will not return before this child finishes anyway.
+
+ }
+ };
+
+ //
+ // Now we execute as the root task
+ //
+ taskReplicaDelegate(null);
+ }
+
+ /// <summary>
+ /// IThreadPoolWorkItem override, which is the entry function for this task when the TP scheduler decides to run it.
+ ///
+ /// </summary>
+ [SecurityCritical]
+ void IThreadPoolWorkItem.ExecuteWorkItem()
+ {
+ ExecuteEntry(false);
+ }
+
+ /// <summary>
+ /// The ThreadPool calls this if a ThreadAbortException is thrown while trying to execute this workitem. This may occur
+ /// before Task would otherwise be able to observe it.
+ /// </summary>
+ [SecurityCritical]
+ void IThreadPoolWorkItem.MarkAborted(ThreadAbortException tae)
+ {
+ // If the task has marked itself as Completed, then it either a) already observed this exception (so we shouldn't handle it here)
+ // or b) completed before the exception ocurred (in which case it shouldn't count against this Task).
+ if (!IsCompleted)
+ {
+ HandleException(tae);
+ FinishThreadAbortedTask(true, false);
+ }
+ }
+
+ /// <summary>
+ /// Outermost entry function to execute this task. Handles all aspects of executing a task on the caller thread.
+ /// Currently this is called by IThreadPoolWorkItem.ExecuteWorkItem(), and TaskManager.TryExecuteInline.
+ ///
+ /// </summary>
+ /// <param name="bPreventDoubleExecution"> Performs atomic updates to prevent double execution. Should only be set to true
+ /// in codepaths servicing user provided TaskSchedulers. The ConcRT or ThreadPool schedulers don't need this. </param>
+ [SecuritySafeCritical]
+ internal bool ExecuteEntry(bool bPreventDoubleExecution)
+ {
+ if (bPreventDoubleExecution || ((Options & (TaskCreationOptions)InternalTaskOptions.SelfReplicating) != 0))
+ {
+ int previousState = 0;
+
+ // Do atomic state transition from queued to invoked. If we observe a task that's already invoked,
+ // we will return false so that TaskScheduler.ExecuteTask can throw an exception back to the custom scheduler.
+ // However we don't want this exception to be throw if the task was already canceled, because it's a
+ // legitimate scenario for custom schedulers to dequeue a task and mark it as canceled (example: throttling scheduler)
+ if (!AtomicStateUpdate(TASK_STATE_DELEGATE_INVOKED,
+ TASK_STATE_DELEGATE_INVOKED | TASK_STATE_COMPLETED_MASK,
+ ref previousState) && (previousState & TASK_STATE_CANCELED) == 0)
+ {
+ // This task has already been invoked. Don't invoke it again.
+ return false;
+ }
+ }
+ else
+ {
+ // Remember that we started running the task delegate.
+ m_stateFlags |= TASK_STATE_DELEGATE_INVOKED;
+ }
+
+ if (!IsCancellationRequested && !IsCanceled)
+ {
+ ExecuteWithThreadLocal(ref t_currentTask);
+ }
+ else if (!IsCanceled)
+ {
+ int prevState = Interlocked.Exchange(ref m_stateFlags, m_stateFlags | TASK_STATE_CANCELED);
+ if ((prevState & TASK_STATE_CANCELED) == 0)
+ {
+ CancellationCleanupLogic();
+ }
+ }
+
+ return true;
+ }
+
+ // A trick so we can refer to the TLS slot with a byref.
+ [SecurityCritical]
+ private void ExecuteWithThreadLocal(ref Task currentTaskSlot)
+ {
+ // Remember the current task so we can restore it after running, and then
+ Task previousTask = currentTaskSlot;
+
+ // ETW event for Task Started
+ var etwLog = TplEtwProvider.Log;
+ Guid savedActivityID = new Guid();
+ bool etwIsEnabled = etwLog.IsEnabled();
+ if (etwIsEnabled)
+ {
+ if (etwLog.TasksSetActivityIds)
+ EventSource.SetCurrentThreadActivityId(TplEtwProvider.CreateGuidForTaskID(this.Id), out savedActivityID);
+ // previousTask holds the actual "current task" we want to report in the event
+ if (previousTask != null)
+ etwLog.TaskStarted(previousTask.m_taskScheduler.Id, previousTask.Id, this.Id);
+ else
+ etwLog.TaskStarted(TaskScheduler.Current.Id, 0, this.Id);
+ }
+
+ if (AsyncCausalityTracer.LoggingOn)
+ AsyncCausalityTracer.TraceSynchronousWorkStart(CausalityTraceLevel.Required, this.Id, CausalitySynchronousWork.Execution);
+
+
+ try
+ {
+ // place the current task into TLS.
+ currentTaskSlot = this;
+
+ ExecutionContext ec = CapturedContext;
+ if (ec == null)
+ {
+ // No context, just run the task directly.
+ Execute();
+ }
+ else
+ {
+ if (IsSelfReplicatingRoot || IsChildReplica)
+ {
+ CapturedContext = CopyExecutionContext(ec);
+ }
+
+ // Run the task. We need a simple shim that converts the
+ // object back into a Task object, so that we can Execute it.
+
+ // Lazily initialize the callback delegate; benign race condition
+ var callback = s_ecCallback;
+ if (callback == null) s_ecCallback = callback = new ContextCallback(ExecutionContextCallback);
+ ExecutionContext.Run(ec, callback, this, true);
+ }
+
+ if (AsyncCausalityTracer.LoggingOn)
+ AsyncCausalityTracer.TraceSynchronousWorkCompletion(CausalityTraceLevel.Required, CausalitySynchronousWork.Execution);
+
+ Finish(true);
+ }
+ finally
+ {
+ currentTaskSlot = previousTask;
+
+ // ETW event for Task Completed
+ if (etwIsEnabled)
+ {
+ // previousTask holds the actual "current task" we want to report in the event
+ if (previousTask != null)
+ etwLog.TaskCompleted(previousTask.m_taskScheduler.Id, previousTask.Id, this.Id, IsFaulted);
+ else
+ etwLog.TaskCompleted(TaskScheduler.Current.Id, 0, this.Id, IsFaulted);
+
+ if (etwLog.TasksSetActivityIds)
+ EventSource.SetCurrentThreadActivityId(savedActivityID);
+ }
+ }
+ }
+
+ // Cached callback delegate that's lazily initialized due to ContextCallback being SecurityCritical
+ [SecurityCritical]
+ private static ContextCallback s_ecCallback;
+
+ [SecurityCritical]
+ private static void ExecutionContextCallback(object obj)
+ {
+ Task task = obj as Task;
+ Contract.Assert(task != null, "expected a task object");
+ task.Execute();
+ }
+
+
+ /// <summary>
+ /// The actual code which invokes the body of the task. This can be overriden in derived types.
+ /// </summary>
+ internal virtual void InnerInvoke()
+ {
+ // Invoke the delegate
+ Contract.Assert(m_action != null, "Null action in InnerInvoke()");
+ var action = m_action as Action;
+ if (action != null)
+ {
+ action();
+ return;
+ }
+ var actionWithState = m_action as Action<object>;
+ if (actionWithState != null)
+ {
+ actionWithState(m_stateObject);
+ return;
+ }
+ Contract.Assert(false, "Invalid m_action in Task");
+ }
+
+ /// <summary>
+ /// Alternate InnerInvoke prototype to be called from ExecuteSelfReplicating() so that
+ /// the Parallel Debugger can discover the actual task being invoked.
+ /// Details: Here, InnerInvoke is actually being called on the rootTask object while we are actually executing the
+ /// childTask. And the debugger needs to discover the childTask, so we pass that down as an argument.
+ /// The NoOptimization and NoInlining flags ensure that the childTask pointer is retained, and that this
+ /// function appears on the callstack.
+ /// </summary>
+ /// <param name="childTask"></param>
+ [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
+ internal void InnerInvokeWithArg(Task childTask)
+ {
+ InnerInvoke();
+ }
+
+ /// <summary>
+ /// Performs whatever handling is necessary for an unhandled exception. Normally
+ /// this just entails adding the exception to the holder object.
+ /// </summary>
+ /// <param name="unhandledException">The exception that went unhandled.</param>
+ private void HandleException(Exception unhandledException)
+ {
+ Contract.Requires(unhandledException != null);
+
+ OperationCanceledException exceptionAsOce = unhandledException as OperationCanceledException;
+ if (exceptionAsOce != null && IsCancellationRequested &&
+ m_contingentProperties.m_cancellationToken == exceptionAsOce.CancellationToken)
+ {
+ // All conditions are satisfied for us to go into canceled state in Finish().
+ // Mark the acknowledgement. The exception is also stored to enable it to be
+ // the exception propagated from an await.
+
+ SetCancellationAcknowledged();
+ AddException(exceptionAsOce, representsCancellation: true);
+ }
+ else
+ {
+ // Other exceptions, including any OCE from the task that doesn't match the tasks' own CT,
+ // or that gets thrown without the CT being set will be treated as an ordinary exception
+ // and added to the aggregate.
+
+ AddException(unhandledException);
+ }
+ }
+
+ #region Await Support
+ /// <summary>Gets an awaiter used to await this <see cref="System.Threading.Tasks.Task"/>.</summary>
+ /// <returns>An awaiter instance.</returns>
+ /// <remarks>This method is intended for compiler user rather than use directly in code.</remarks>
+ public TaskAwaiter GetAwaiter()
+ {
+ return new TaskAwaiter(this);
+ }
+
+ /// <summary>Configures an awaiter used to await this <see cref="System.Threading.Tasks.Task"/>.</summary>
+ /// <param name="continueOnCapturedContext">
+ /// true to attempt to marshal the continuation back to the original context captured; otherwise, false.
+ /// </param>
+ /// <returns>An object used to await this task.</returns>
+ public ConfiguredTaskAwaitable ConfigureAwait(bool continueOnCapturedContext)
+ {
+ return new ConfiguredTaskAwaitable(this, continueOnCapturedContext);
+ }
+
+ /// <summary>
+ /// Sets a continuation onto the <see cref="System.Threading.Tasks.Task"/>.
+ /// The continuation is scheduled to run in the current synchronization context is one exists,
+ /// otherwise in the current task scheduler.
+ /// </summary>
+ /// <param name="continuationAction">The action to invoke when the <see cref="System.Threading.Tasks.Task"/> has completed.</param>
+ /// <param name="continueOnCapturedContext">
+ /// true to attempt to marshal the continuation back to the original context captured; otherwise, false.
+ /// </param>
+ /// <param name="flowExecutionContext">Whether to flow ExecutionContext across the await.</param>
+ /// <param name="stackMark">A stack crawl mark tied to execution context.</param>
+ /// <exception cref="System.InvalidOperationException">The awaiter was not properly initialized.</exception>
+ [SecurityCritical]
+ internal void SetContinuationForAwait(
+ Action continuationAction, bool continueOnCapturedContext, bool flowExecutionContext, ref StackCrawlMark stackMark)
+ {
+ Contract.Requires(continuationAction != null);
+
+ // Create the best AwaitTaskContinuation object given the request.
+ // If this remains null by the end of the function, we can use the
+ // continuationAction directly without wrapping it.
+ TaskContinuation tc = null;
+
+ // If the user wants the continuation to run on the current "context" if there is one...
+ if (continueOnCapturedContext)
+ {
+ // First try getting the current synchronization context.
+ // If the current context is really just the base SynchronizationContext type,
+ // which is intended to be equivalent to not having a current SynchronizationContext at all,
+ // then ignore it. This helps with performance by avoiding unnecessary posts and queueing
+ // of work items, but more so it ensures that if code happens to publish the default context
+ // as current, it won't prevent usage of a current task scheduler if there is one.
+ var syncCtx = SynchronizationContext.CurrentNoFlow;
+ if (syncCtx != null && syncCtx.GetType() != typeof(SynchronizationContext))
+ {
+ tc = new SynchronizationContextAwaitTaskContinuation(syncCtx, continuationAction, flowExecutionContext, ref stackMark);
+ }
+ else
+ {
+ // If there was no SynchronizationContext, then try for the current scheduler.
+ // We only care about it if it's not the default.
+ var scheduler = TaskScheduler.InternalCurrent;
+ if (scheduler != null && scheduler != TaskScheduler.Default)
+ {
+ tc = new TaskSchedulerAwaitTaskContinuation(scheduler, continuationAction, flowExecutionContext, ref stackMark);
+ }
+ }
+ }
+
+ if (tc == null && flowExecutionContext)
+ {
+ // We're targeting the default scheduler, so we can use the faster path
+ // that assumes the default, and thus we don't need to store it. If we're flowing
+ // ExecutionContext, we need to capture it and wrap it in an AwaitTaskContinuation.
+ // Otherwise, we're targeting the default scheduler and we don't need to flow ExecutionContext, so
+ // we don't actually need a continuation object. We can just store/queue the action itself.
+ tc = new AwaitTaskContinuation(continuationAction, flowExecutionContext: true, stackMark: ref stackMark);
+ }
+
+ // Now register the continuation, and if we couldn't register it because the task is already completing,
+ // process the continuation directly (in which case make sure we schedule the continuation
+ // rather than inlining it, the latter of which could result in a rare but possible stack overflow).
+ if (tc != null)
+ {
+ if (!AddTaskContinuation(tc, addBeforeOthers: false))
+ tc.Run(this, bCanInlineContinuationTask: false);
+ }
+ else
+ {
+ Contract.Assert(!flowExecutionContext, "We already determined we're not required to flow context.");
+ if (!AddTaskContinuation(continuationAction, addBeforeOthers: false))
+ AwaitTaskContinuation.UnsafeScheduleAction(continuationAction, this);
+ }
+ }
+
+ /// <summary>Creates an awaitable that asynchronously yields back to the current context when awaited.</summary>
+ /// <returns>
+ /// A context that, when awaited, will asynchronously transition back into the current context at the
+ /// time of the await. If the current SynchronizationContext is non-null, that is treated as the current context.
+ /// Otherwise, TaskScheduler.Current is treated as the current context.
+ /// </returns>
+ public static YieldAwaitable Yield()
+ {
+ return new YieldAwaitable();
+ }
+ #endregion
+
+ /// <summary>
+ /// Waits for the <see cref="Task"/> to complete execution.
+ /// </summary>
+ /// <exception cref="T:System.AggregateException">
+ /// The <see cref="Task"/> was canceled -or- an exception was thrown during
+ /// the execution of the <see cref="Task"/>.
+ /// </exception>
+ public void Wait()
+ {
+#if DEBUG
+ bool waitResult =
+#endif
+ Wait(Timeout.Infinite, default(CancellationToken));
+
+#if DEBUG
+ Contract.Assert(waitResult, "expected wait to succeed");
+#endif
+ }
+
+ /// <summary>
+ /// Waits for the <see cref="Task"/> to complete execution.
+ /// </summary>
+ /// <param name="timeout">
+ /// A <see cref="System.TimeSpan"/> that represents the number of milliseconds to wait, or a <see
+ /// cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely.
+ /// </param>
+ /// <returns>
+ /// true if the <see cref="Task"/> completed execution within the allotted time; otherwise, false.
+ /// </returns>
+ /// <exception cref="T:System.AggregateException">
+ /// The <see cref="Task"/> was canceled -or- an exception was thrown during the execution of the <see
+ /// cref="Task"/>.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentOutOfRangeException">
+ /// <paramref name="timeout"/> is a negative number other than -1 milliseconds, which represents an
+ /// infinite time-out -or- timeout is greater than
+ /// <see cref="System.Int32.MaxValue"/>.
+ /// </exception>
+ public bool Wait(TimeSpan timeout)
+ {
+ long totalMilliseconds = (long)timeout.TotalMilliseconds;
+ if (totalMilliseconds < -1 || totalMilliseconds > Int32.MaxValue)
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.timeout);
+ }
+
+ return Wait((int)totalMilliseconds, default(CancellationToken));
+ }
+
+
+ /// <summary>
+ /// Waits for the <see cref="Task"/> to complete execution.
+ /// </summary>
+ /// <param name="cancellationToken">
+ /// A <see cref="CancellationToken"/> to observe while waiting for the task to complete.
+ /// </param>
+ /// <exception cref="T:System.OperationCanceledException">
+ /// The <paramref name="cancellationToken"/> was canceled.
+ /// </exception>
+ /// <exception cref="T:System.AggregateException">
+ /// The <see cref="Task"/> was canceled -or- an exception was thrown during the execution of the <see
+ /// cref="Task"/>.
+ /// </exception>
+ public void Wait(CancellationToken cancellationToken)
+ {
+ Wait(Timeout.Infinite, cancellationToken);
+ }
+
+
+ /// <summary>
+ /// Waits for the <see cref="Task"/> to complete execution.
+ /// </summary>
+ /// <param name="millisecondsTimeout">
+ /// The number of milliseconds to wait, or <see cref="System.Threading.Timeout.Infinite"/> (-1) to
+ /// wait indefinitely.</param>
+ /// <returns>true if the <see cref="Task"/> completed execution within the allotted time; otherwise,
+ /// false.
+ /// </returns>
+ /// <exception cref="T:System.ArgumentOutOfRangeException">
+ /// <paramref name="millisecondsTimeout"/> is a negative number other than -1, which represents an
+ /// infinite time-out.
+ /// </exception>
+ /// <exception cref="T:System.AggregateException">
+ /// The <see cref="Task"/> was canceled -or- an exception was thrown during the execution of the <see
+ /// cref="Task"/>.
+ /// </exception>
+ public bool Wait(int millisecondsTimeout)
+ {
+ return Wait(millisecondsTimeout, default(CancellationToken));
+ }
+
+
+ /// <summary>
+ /// Waits for the <see cref="Task"/> to complete execution.
+ /// </summary>
+ /// <param name="millisecondsTimeout">
+ /// The number of milliseconds to wait, or <see cref="System.Threading.Timeout.Infinite"/> (-1) to
+ /// wait indefinitely.
+ /// </param>
+ /// <param name="cancellationToken">
+ /// A <see cref="CancellationToken"/> to observe while waiting for the task to complete.
+ /// </param>
+ /// <returns>
+ /// true if the <see cref="Task"/> completed execution within the allotted time; otherwise, false.
+ /// </returns>
+ /// <exception cref="T:System.AggregateException">
+ /// The <see cref="Task"/> was canceled -or- an exception was thrown during the execution of the <see
+ /// cref="Task"/>.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentOutOfRangeException">
+ /// <paramref name="millisecondsTimeout"/> is a negative number other than -1, which represents an
+ /// infinite time-out.
+ /// </exception>
+ /// <exception cref="T:System.OperationCanceledException">
+ /// The <paramref name="cancellationToken"/> was canceled.
+ /// </exception>
+ public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken)
+ {
+ if (millisecondsTimeout < -1)
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.millisecondsTimeout);
+ }
+ Contract.EndContractBlock();
+
+ // Return immediately if we know that we've completed "clean" -- no exceptions, no cancellations
+ // and if no notification to the debugger is required
+ if (!IsWaitNotificationEnabledOrNotRanToCompletion) // (!DebuggerBitSet && RanToCompletion)
+ return true;
+
+ // Wait, and then return if we're still not done.
+ if (!InternalWait(millisecondsTimeout, cancellationToken))
+ return false;
+
+ if (IsWaitNotificationEnabledOrNotRanToCompletion) // avoid a few unnecessary volatile reads if we completed successfully
+ {
+ // Notify the debugger of the wait completion if it's requested such a notification
+ NotifyDebuggerOfWaitCompletionIfNecessary();
+
+ // If cancellation was requested and the task was canceled, throw an
+ // OperationCanceledException. This is prioritized ahead of the ThrowIfExceptional
+ // call to bring more determinism to cases where the same token is used to
+ // cancel the Wait and to cancel the Task. Otherwise, there's a race condition between
+ // whether the Wait or the Task observes the cancellation request first,
+ // and different exceptions result from the different cases.
+ if (IsCanceled) cancellationToken.ThrowIfCancellationRequested();
+
+ // If an exception occurred, or the task was cancelled, throw an exception.
+ ThrowIfExceptional(true);
+ }
+
+ Contract.Assert((m_stateFlags & TASK_STATE_FAULTED) == 0, "Task.Wait() completing when in Faulted state.");
+
+ return true;
+ }
+
+ // Convenience method that wraps any scheduler exception in a TaskSchedulerException
+ // and rethrows it.
+ private bool WrappedTryRunInline()
+ {
+ if (m_taskScheduler == null)
+ return false;
+
+ try
+ {
+ return m_taskScheduler.TryRunInline(this, true);
+ }
+ catch (Exception e)
+ {
+ // we 1) either received an unexpected exception originating from a custom scheduler, which needs to be wrapped in a TSE and thrown
+ // 2) or a a ThreadAbortException, which we need to skip here, because it would already have been handled in Task.Execute
+ if (!(e is ThreadAbortException))
+ {
+ TaskSchedulerException tse = new TaskSchedulerException(e);
+ throw tse;
+ }
+ else
+ {
+ throw;
+ }
+ }
+ }
+
+ /// <summary>
+ /// The core wait function, which is only accesible internally. It's meant to be used in places in TPL code where
+ /// the current context is known or cached.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
+ internal bool InternalWait(int millisecondsTimeout, CancellationToken cancellationToken)
+ {
+ // ETW event for Task Wait Begin
+ var etwLog = TplEtwProvider.Log;
+ bool etwIsEnabled = etwLog.IsEnabled();
+ if (etwIsEnabled)
+ {
+ Task currentTask = Task.InternalCurrent;
+ etwLog.TaskWaitBegin(
+ (currentTask != null ? currentTask.m_taskScheduler.Id : TaskScheduler.Default.Id), (currentTask != null ? currentTask.Id : 0),
+ this.Id, TplEtwProvider.TaskWaitBehavior.Synchronous, 0, System.Threading.Thread.GetDomainID());
+ }
+
+ bool returnValue = IsCompleted;
+
+ // If the event hasn't already been set, we will wait.
+ if (!returnValue)
+ {
+ // Alert a listening debugger that we can't make forward progress unless it slips threads.
+ // We call NOCTD for two reasons:
+ // 1. If the task runs on another thread, then we'll be blocked here indefinitely.
+ // 2. If the task runs inline but takes some time to complete, it will suffer ThreadAbort with possible state corruption,
+ // and it is best to prevent this unless the user explicitly asks to view the value with thread-slipping enabled.
+ Debugger.NotifyOfCrossThreadDependency();
+
+ // We will attempt inline execution only if an infinite wait was requested
+ // Inline execution doesn't make sense for finite timeouts and if a cancellation token was specified
+ // because we don't know how long the task delegate will take.
+ if (millisecondsTimeout == Timeout.Infinite && !cancellationToken.CanBeCanceled &&
+ WrappedTryRunInline() && IsCompleted) // TryRunInline doesn't guarantee completion, as there may be unfinished children.
+ {
+ returnValue = true;
+ }
+ else
+ {
+ returnValue = SpinThenBlockingWait(millisecondsTimeout, cancellationToken);
+ }
+ }
+
+ Contract.Assert(IsCompleted || millisecondsTimeout != Timeout.Infinite);
+
+ // ETW event for Task Wait End
+ if (etwIsEnabled)
+ {
+ Task currentTask = Task.InternalCurrent;
+ if (currentTask != null)
+ {
+ etwLog.TaskWaitEnd(currentTask.m_taskScheduler.Id, currentTask.Id, this.Id);
+ }
+ else
+ {
+ etwLog.TaskWaitEnd(TaskScheduler.Default.Id, 0, this.Id);
+ }
+ // logically the continuation is empty so we immediately fire
+ etwLog.TaskWaitContinuationComplete(this.Id);
+ }
+
+ return returnValue;
+ }
+
+ // An MRES that gets set when Invoke is called. This replaces old logic that looked like this:
+ // ManualResetEventSlim mres = new ManualResetEventSlim(false, 0);
+ // Action<Task> completionAction = delegate {mres.Set();}
+ // AddCompletionAction(completionAction);
+ // with this:
+ // SetOnInvokeMres mres = new SetOnInvokeMres();
+ // AddCompletionAction(mres, addBeforeOthers: true);
+ // which saves a couple of allocations.
+ //
+ // Used in SpinThenBlockingWait (below), but could be seen as a general purpose mechanism.
+ private sealed class SetOnInvokeMres : ManualResetEventSlim, ITaskCompletionAction
+ {
+ internal SetOnInvokeMres() : base(false, 0) { }
+ public void Invoke(Task completingTask) { Set(); }
+ public bool InvokeMayRunArbitraryCode { get { return false; } }
+ }
+
+ /// <summary>
+ /// Waits for the task to complete, for a timeout to occur, or for cancellation to be requested.
+ /// The method first spins and then falls back to blocking on a new event.
+ /// </summary>
+ /// <param name="millisecondsTimeout">The timeout.</param>
+ /// <param name="cancellationToken">The token.</param>
+ /// <returns>true if the task is completed; otherwise, false.</returns>
+ private bool SpinThenBlockingWait(int millisecondsTimeout, CancellationToken cancellationToken)
+ {
+ bool infiniteWait = millisecondsTimeout == Timeout.Infinite;
+ uint startTimeTicks = infiniteWait ? 0 : (uint)Environment.TickCount;
+ bool returnValue = SpinWait(millisecondsTimeout);
+ if (!returnValue)
+ {
+ var mres = new SetOnInvokeMres();
+ try
+ {
+ AddCompletionAction(mres, addBeforeOthers: true);
+ if (infiniteWait)
+ {
+ returnValue = mres.Wait(Timeout.Infinite, cancellationToken);
+ }
+ else
+ {
+ uint elapsedTimeTicks = ((uint)Environment.TickCount) - startTimeTicks;
+ if (elapsedTimeTicks < millisecondsTimeout)
+ {
+ returnValue = mres.Wait((int)(millisecondsTimeout - elapsedTimeTicks), cancellationToken);
+ }
+ }
+ }
+ finally
+ {
+ if (!IsCompleted) RemoveContinuation(mres);
+ // Don't Dispose of the MRES, because the continuation off of this task may
+ // still be running. This is ok, however, as we never access the MRES' WaitHandle,
+ // and thus no finalizable resources are actually allocated.
+ }
+ }
+ return returnValue;
+ }
+
+ /// <summary>
+ /// Spins briefly while checking IsCompleted
+ /// </summary>
+ /// <param name="millisecondsTimeout">The timeout.</param>
+ /// <returns>true if the task is completed; otherwise, false.</returns>
+ /// <exception cref="System.OperationCanceledException">The wait was canceled.</exception>
+ private bool SpinWait(int millisecondsTimeout)
+ {
+ if (IsCompleted) return true;
+
+ if (millisecondsTimeout == 0)
+ {
+ // For 0-timeouts, we just return immediately.
+ return false;
+ }
+
+ //This code is pretty similar to the custom spinning in MRES except there is no yieling after we exceed the spin count
+ int spinCount = PlatformHelper.IsSingleProcessor ? 1 : System.Threading.SpinWait.YIELD_THRESHOLD; //spin only once if we are running on a single CPU
+ for (int i = 0; i < spinCount; i++)
+ {
+ if (IsCompleted)
+ {
+ return true;
+ }
+
+ if (i == spinCount / 2)
+ {
+ Thread.Yield();
+ }
+ else
+ {
+ Thread.SpinWait(PlatformHelper.ProcessorCount * (4 << i));
+ }
+
+ }
+
+ return IsCompleted;
+ }
+
+ /// <summary>
+ /// Cancels the <see cref="Task"/>.
+ /// </summary>
+ /// <param name="bCancelNonExecutingOnly">
+ /// Indicates whether we should only cancel non-invoked tasks.
+ /// For the default scheduler this option will only be serviced through TryDequeue.
+ /// For custom schedulers we also attempt an atomic state transition.
+ /// </param>
+ /// <returns>true if the task was successfully canceled; otherwise, false.</returns>
+ [SecuritySafeCritical]
+ internal bool InternalCancel(bool bCancelNonExecutingOnly)
+ {
+ Contract.Requires((Options & (TaskCreationOptions)InternalTaskOptions.PromiseTask) == 0, "Task.InternalCancel() did not expect promise-style task");
+
+ bool bPopSucceeded = false;
+ bool mustCleanup = false;
+
+ TaskSchedulerException tse = null;
+
+ // If started, and running in a task context, we can try to pop the chore.
+ if ((m_stateFlags & TASK_STATE_STARTED) != 0)
+ {
+ TaskScheduler ts = m_taskScheduler;
+
+ try
+ {
+ bPopSucceeded = (ts != null) && ts.TryDequeue(this);
+ }
+ catch (Exception e)
+ {
+ // TryDequeue threw. We don't know whether the task was properly dequeued or not. So we must let the rest of
+ // the cancellation logic run its course (record the request, attempt atomic state transition and do cleanup where appropriate)
+ // Here we will only record a TaskSchedulerException, which will later be thrown at function exit.
+
+ if (!(e is ThreadAbortException))
+ {
+ tse = new TaskSchedulerException(e);
+ }
+ }
+
+ bool bRequiresAtomicStartTransition = (ts != null && ts.RequiresAtomicStartTransition) || ((Options & (TaskCreationOptions)InternalTaskOptions.SelfReplicating) != 0);
+
+ if (!bPopSucceeded && bCancelNonExecutingOnly && bRequiresAtomicStartTransition)
+ {
+ // The caller requested cancellation of non-invoked tasks only, and TryDequeue was one way of doing it...
+ // Since that seems to have failed, we should now try an atomic state transition (from non-invoked state to canceled)
+ // An atomic transition here is only safe if we know we're on a custom task scheduler, which also forces a CAS on ExecuteEntry
+
+ // Even though this task can't have any children, we should be ready for handling any continuations that
+ // may be attached to it (although currently
+ // So we need to remeber whether we actually did the flip, so we can do clean up (finish continuations etc)
+ mustCleanup = AtomicStateUpdate(TASK_STATE_CANCELED, TASK_STATE_DELEGATE_INVOKED | TASK_STATE_CANCELED);
+
+
+ // PS: This is slightly different from the regular cancellation codepath
+ // since we record the cancellation request *after* doing the state transition.
+ // However that shouldn't matter too much because the task was never invoked, thus can't have children
+ }
+
+ }
+
+ if (!bCancelNonExecutingOnly || bPopSucceeded || mustCleanup)
+ {
+ // Record the cancellation request.
+ RecordInternalCancellationRequest();
+
+ // Determine whether we need to clean up
+ // This will be the case
+ // 1) if we were able to pop, and we are able to update task state to TASK_STATE_CANCELED
+ // 2) if the task seems to be yet unstarted, and we can transition to
+ // TASK_STATE_CANCELED before anyone else can transition into _STARTED or _CANCELED or
+ // _RAN_TO_COMPLETION or _FAULTED
+ // Note that we do not check for TASK_STATE_COMPLETION_RESERVED. That only applies to promise-style
+ // tasks, and a promise-style task should not enter into this codepath.
+ if (bPopSucceeded)
+ {
+ // hitting this would mean something wrong with the AtomicStateUpdate above
+ Contract.Assert(!mustCleanup, "Possibly an invalid state transition call was made in InternalCancel()");
+
+ // Include TASK_STATE_DELEGATE_INVOKED in "illegal" bits to protect against the situation where
+ // TS.TryDequeue() returns true but the task is still left on the queue.
+ mustCleanup = AtomicStateUpdate(TASK_STATE_CANCELED, TASK_STATE_CANCELED | TASK_STATE_DELEGATE_INVOKED);
+ }
+ else if (!mustCleanup && (m_stateFlags & TASK_STATE_STARTED) == 0)
+ {
+ mustCleanup = AtomicStateUpdate(TASK_STATE_CANCELED,
+ TASK_STATE_CANCELED | TASK_STATE_STARTED | TASK_STATE_RAN_TO_COMPLETION |
+ TASK_STATE_FAULTED | TASK_STATE_DELEGATE_INVOKED);
+ }
+
+ // do the cleanup (i.e. set completion event and finish continuations)
+ if (mustCleanup)
+ {
+ CancellationCleanupLogic();
+ }
+ }
+
+ if (tse != null)
+ throw tse;
+ else
+ return (mustCleanup);
+ }
+
+ // Breaks out logic for recording a cancellation request
+ internal void RecordInternalCancellationRequest()
+ {
+ // Record the cancellation request.
+ EnsureContingentPropertiesInitialized().m_internalCancellationRequested = CANCELLATION_REQUESTED;
+ }
+
+ // Breaks out logic for recording a cancellation request
+ // This overload should only be used for promise tasks where no cancellation token
+ // was supplied when the task was created.
+ internal void RecordInternalCancellationRequest(CancellationToken tokenToRecord)
+ {
+ RecordInternalCancellationRequest();
+
+ Contract.Assert((Options & (TaskCreationOptions)InternalTaskOptions.PromiseTask) != 0, "Task.RecordInternalCancellationRequest(CancellationToken) only valid for promise-style task");
+ Contract.Assert(m_contingentProperties.m_cancellationToken == default(CancellationToken));
+
+ // Store the supplied cancellation token as this task's token.
+ // Waiting on this task will then result in an OperationCanceledException containing this token.
+ if (tokenToRecord != default(CancellationToken))
+ {
+ m_contingentProperties.m_cancellationToken = tokenToRecord;
+ }
+ }
+
+ // Breaks out logic for recording a cancellation request
+ // This overload should only be used for promise tasks where no cancellation token
+ // was supplied when the task was created.
+ internal void RecordInternalCancellationRequest(CancellationToken tokenToRecord, object cancellationException)
+ {
+ RecordInternalCancellationRequest(tokenToRecord);
+
+ // Store the supplied cancellation exception
+ if (cancellationException != null)
+ {
+#if DEBUG
+ var oce = cancellationException as OperationCanceledException;
+ if (oce == null)
+ {
+ var edi = cancellationException as ExceptionDispatchInfo;
+ Contract.Assert(edi != null, "Expected either an OCE or an EDI");
+ oce = edi.SourceException as OperationCanceledException;
+ Contract.Assert(oce != null, "Expected EDI to contain an OCE");
+ }
+ Contract.Assert(oce.CancellationToken == tokenToRecord,
+ "Expected OCE's token to match the provided token.");
+#endif
+ AddException(cancellationException, representsCancellation: true);
+ }
+ }
+
+ // ASSUMES THAT A SUCCESSFUL CANCELLATION HAS JUST OCCURRED ON THIS TASK!!!
+ // And this method should be called at most once per task.
+ internal void CancellationCleanupLogic()
+ {
+ Contract.Assert((m_stateFlags & (TASK_STATE_CANCELED | TASK_STATE_COMPLETION_RESERVED)) != 0, "Task.CancellationCleanupLogic(): Task not canceled or reserved.");
+ // I'd like to do this, but there is a small window for a race condition. If someone calls Wait() between InternalCancel() and
+ // here, that will set m_completionEvent, leading to a meaningless/harmless assertion.
+ //Contract.Assert((m_completionEvent == null) || !m_completionEvent.IsSet, "Task.CancellationCleanupLogic(): Completion event already set.");
+
+ // This may have been set already, but we need to make sure.
+ Interlocked.Exchange(ref m_stateFlags, m_stateFlags | TASK_STATE_CANCELED);
+
+ // Fire completion event if it has been lazily initialized
+ var cp = Volatile.Read(ref m_contingentProperties);
+ if (cp != null)
+ {
+ cp.SetCompleted();
+ cp.DeregisterCancellationCallback();
+ }
+
+ if (AsyncCausalityTracer.LoggingOn)
+ AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, this.Id, AsyncCausalityStatus.Canceled);
+
+ if (Task.s_asyncDebuggingEnabled)
+ {
+ RemoveFromActiveTasks(this.Id);
+ }
+
+ // Notify parents, fire continuations, other cleanup.
+ FinishStageThree();
+ }
+
+
+ /// <summary>
+ /// Sets the task's cancellation acknowledged flag.
+ /// </summary>
+ private void SetCancellationAcknowledged()
+ {
+ Contract.Assert(this == Task.InternalCurrent, "SetCancellationAcknowledged() should only be called while this is still the current task");
+ Contract.Assert(IsCancellationRequested, "SetCancellationAcknowledged() should not be called if the task's CT wasn't signaled");
+
+ m_stateFlags |= TASK_STATE_CANCELLATIONACKNOWLEDGED;
+ }
+
+
+ //
+ // Continuation passing functionality (aka ContinueWith)
+ //
+
+
+
+
+ /// <summary>
+ /// Runs all of the continuations, as appropriate.
+ /// </summary>
+ [SecuritySafeCritical] // for AwaitTaskContinuation.RunOrScheduleAction
+ internal void FinishContinuations()
+ {
+ // Atomically store the fact that this task is completing. From this point on, the adding of continuations will
+ // result in the continuations being run/launched directly rather than being added to the continuation list.
+ object continuationObject = Interlocked.Exchange(ref m_continuationObject, s_taskCompletionSentinel);
+ TplEtwProvider etw = TplEtwProvider.Log;
+ bool tplEtwProviderLoggingEnabled = etw.IsEnabled();
+ if (tplEtwProviderLoggingEnabled)
+ {
+ etw.RunningContinuation(Id, continuationObject);
+ }
+
+ // If continuationObject == null, then we don't have any continuations to process
+ if (continuationObject != null)
+ {
+
+ if (AsyncCausalityTracer.LoggingOn)
+ AsyncCausalityTracer.TraceSynchronousWorkStart(CausalityTraceLevel.Required, this.Id, CausalitySynchronousWork.CompletionNotification);
+
+ // Skip synchronous execution of continuations if this task's thread was aborted
+ bool bCanInlineContinuations = !(((m_stateFlags & TASK_STATE_THREAD_WAS_ABORTED) != 0) ||
+ (Thread.CurrentThread.ThreadState == ThreadState.AbortRequested) ||
+ ((m_stateFlags & (int)TaskCreationOptions.RunContinuationsAsynchronously) != 0));
+
+ // Handle the single-Action case
+ Action singleAction = continuationObject as Action;
+ if (singleAction != null)
+ {
+ AwaitTaskContinuation.RunOrScheduleAction(singleAction, bCanInlineContinuations, ref t_currentTask);
+ LogFinishCompletionNotification();
+ return;
+ }
+
+ // Handle the single-ITaskCompletionAction case
+ ITaskCompletionAction singleTaskCompletionAction = continuationObject as ITaskCompletionAction;
+ if (singleTaskCompletionAction != null)
+ {
+ if (bCanInlineContinuations || !singleTaskCompletionAction.InvokeMayRunArbitraryCode)
+ {
+ singleTaskCompletionAction.Invoke(this);
+ }
+ else
+ {
+ ThreadPool.UnsafeQueueCustomWorkItem(new CompletionActionInvoker(singleTaskCompletionAction, this), forceGlobal: false);
+ }
+ LogFinishCompletionNotification();
+ return;
+ }
+
+ // Handle the single-TaskContinuation case
+ TaskContinuation singleTaskContinuation = continuationObject as TaskContinuation;
+ if (singleTaskContinuation != null)
+ {
+ singleTaskContinuation.Run(this, bCanInlineContinuations);
+ LogFinishCompletionNotification();
+ return;
+ }
+
+ // Not a single; attempt to cast as list
+ List<object> continuations = continuationObject as List<object>;
+
+ if (continuations == null)
+ {
+ LogFinishCompletionNotification();
+ return; // Not a single or a list; just return
+ }
+
+ //
+ // Begin processing of continuation list
+ //
+
+ // Wait for any concurrent adds or removes to be retired
+ lock (continuations) { }
+ int continuationCount = continuations.Count;
+
+ // Fire the asynchronous continuations first ...
+ for (int i = 0; i < continuationCount; i++)
+ {
+ // Synchronous continuation tasks will have the ExecuteSynchronously option,
+ // and we're looking for asynchronous tasks...
+ var tc = continuations[i] as StandardTaskContinuation;
+ if (tc != null && (tc.m_options & TaskContinuationOptions.ExecuteSynchronously) == 0)
+ {
+ if (tplEtwProviderLoggingEnabled)
+ {
+ etw.RunningContinuationList(Id, i, tc);
+ }
+ continuations[i] = null; // so that we can skip this later
+ tc.Run(this, bCanInlineContinuations);
+ }
+ }
+
+ // ... and then fire the synchronous continuations (if there are any).
+ // This includes ITaskCompletionAction, AwaitTaskContinuations, and
+ // Action delegates, which are all by default implicitly synchronous.
+ for (int i = 0; i < continuationCount; i++)
+ {
+ object currentContinuation = continuations[i];
+ if (currentContinuation == null) continue;
+ continuations[i] = null; // to enable free'ing up memory earlier
+ if (tplEtwProviderLoggingEnabled)
+ {
+ etw.RunningContinuationList(Id, i, currentContinuation);
+ }
+
+ // If the continuation is an Action delegate, it came from an await continuation,
+ // and we should use AwaitTaskContinuation to run it.
+ Action ad = currentContinuation as Action;
+ if (ad != null)
+ {
+ AwaitTaskContinuation.RunOrScheduleAction(ad, bCanInlineContinuations, ref t_currentTask);
+ }
+ else
+ {
+ // If it's a TaskContinuation object of some kind, invoke it.
+ TaskContinuation tc = currentContinuation as TaskContinuation;
+ if (tc != null)
+ {
+ // We know that this is a synchronous continuation because the
+ // asynchronous ones have been weeded out
+ tc.Run(this, bCanInlineContinuations);
+ }
+ // Otherwise, it must be an ITaskCompletionAction, so invoke it.
+ else
+ {
+ Contract.Assert(currentContinuation is ITaskCompletionAction, "Expected continuation element to be Action, TaskContinuation, or ITaskContinuationAction");
+ var action = (ITaskCompletionAction)currentContinuation;
+
+ if (bCanInlineContinuations || !action.InvokeMayRunArbitraryCode)
+ {
+ action.Invoke(this);
+ }
+ else
+ {
+ ThreadPool.UnsafeQueueCustomWorkItem(new CompletionActionInvoker(action, this), forceGlobal: false);
+ }
+ }
+ }
+ }
+
+ LogFinishCompletionNotification();
+ }
+ }
+
+ private void LogFinishCompletionNotification()
+ {
+ if (AsyncCausalityTracer.LoggingOn)
+ AsyncCausalityTracer.TraceSynchronousWorkCompletion(CausalityTraceLevel.Required, CausalitySynchronousWork.CompletionNotification);
+ }
+
+ #region Continuation methods
+
+ #region Action<Task> continuation
+ /// <summary>
+ /// Creates a continuation that executes when the target <see cref="Task"/> completes.
+ /// </summary>
+ /// <param name="continuationAction">
+ /// An action to run when the <see cref="Task"/> completes. When run, the delegate will be
+ /// passed the completed task as an argument.
+ /// </param>
+ /// <returns>A new continuation <see cref="Task"/>.</returns>
+ /// <remarks>
+ /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has
+ /// completed, whether it completes due to running to completion successfully, faulting due to an
+ /// unhandled exception, or exiting out early due to being canceled.
+ /// </remarks>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="continuationAction"/> argument is null.
+ /// </exception>
+ [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable
+ public Task ContinueWith(Action<Task> continuationAction)
+ {
+ StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
+ return ContinueWith(continuationAction, TaskScheduler.Current, default(CancellationToken), TaskContinuationOptions.None, ref stackMark);
+ }
+
+ /// <summary>
+ /// Creates a continuation that executes when the target <see cref="Task"/> completes.
+ /// </summary>
+ /// <param name="continuationAction">
+ /// An action to run when the <see cref="Task"/> completes. When run, the delegate will be
+ /// passed the completed task as an argument.
+ /// </param>
+ /// <param name="cancellationToken"> The <see cref="CancellationToken"/> that will be assigned to the new continuation task.</param>
+ /// <returns>A new continuation <see cref="Task"/>.</returns>
+ /// <remarks>
+ /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has
+ /// completed, whether it completes due to running to completion successfully, faulting due to an
+ /// unhandled exception, or exiting out early due to being canceled.
+ /// </remarks>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="continuationAction"/> argument is null.
+ /// </exception>
+ /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see>
+ /// has already been disposed.
+ /// </exception>
+ [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable
+ public Task ContinueWith(Action<Task> continuationAction, CancellationToken cancellationToken)
+ {
+ StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
+ return ContinueWith(continuationAction, TaskScheduler.Current, cancellationToken, TaskContinuationOptions.None, ref stackMark);
+ }
+
+ /// <summary>
+ /// Creates a continuation that executes when the target <see cref="Task"/> completes.
+ /// </summary>
+ /// <param name="continuationAction">
+ /// An action to run when the <see cref="Task"/> completes. When run, the delegate will be
+ /// passed the completed task as an argument.
+ /// </param>
+ /// <param name="scheduler">
+ /// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its execution.
+ /// </param>
+ /// <returns>A new continuation <see cref="Task"/>.</returns>
+ /// <remarks>
+ /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has
+ /// completed, whether it completes due to running to completion successfully, faulting due to an
+ /// unhandled exception, or exiting out early due to being canceled.
+ /// </remarks>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="continuationAction"/> argument is null.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="scheduler"/> argument is null.
+ /// </exception>
+ [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable
+ public Task ContinueWith(Action<Task> continuationAction, TaskScheduler scheduler)
+ {
+ StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
+ return ContinueWith(continuationAction, scheduler, default(CancellationToken), TaskContinuationOptions.None, ref stackMark);
+ }
+
+ /// <summary>
+ /// Creates a continuation that executes when the target <see cref="Task"/> completes.
+ /// </summary>
+ /// <param name="continuationAction">
+ /// An action to run when the <see cref="Task"/> completes. When run, the delegate will be
+ /// passed the completed task as an argument.
+ /// </param>
+ /// <param name="continuationOptions">
+ /// Options for when the continuation is scheduled and how it behaves. This includes criteria, such
+ /// as <see
+ /// cref="System.Threading.Tasks.TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as
+ /// well as execution options, such as <see
+ /// cref="System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>.
+ /// </param>
+ /// <returns>A new continuation <see cref="Task"/>.</returns>
+ /// <remarks>
+ /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has
+ /// completed. If the continuation criteria specified through the <paramref
+ /// name="continuationOptions"/> parameter are not met, the continuation task will be canceled
+ /// instead of scheduled.
+ /// </remarks>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="continuationAction"/> argument is null.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentOutOfRangeException">
+ /// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see
+ /// cref="T:System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>.
+ /// </exception>
+ [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable
+ public Task ContinueWith(Action<Task> continuationAction, TaskContinuationOptions continuationOptions)
+ {
+ StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
+ return ContinueWith(continuationAction, TaskScheduler.Current, default(CancellationToken), continuationOptions, ref stackMark);
+ }
+
+ /// <summary>
+ /// Creates a continuation that executes when the target <see cref="Task"/> completes.
+ /// </summary>
+ /// <param name="continuationAction">
+ /// An action to run when the <see cref="Task"/> completes. When run, the delegate will be
+ /// passed the completed task as an argument.
+ /// </param>
+ /// <param name="continuationOptions">
+ /// Options for when the continuation is scheduled and how it behaves. This includes criteria, such
+ /// as <see
+ /// cref="System.Threading.Tasks.TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as
+ /// well as execution options, such as <see
+ /// cref="System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>.
+ /// </param>
+ /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new continuation task.</param>
+ /// <param name="scheduler">
+ /// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its
+ /// execution.
+ /// </param>
+ /// <returns>A new continuation <see cref="Task"/>.</returns>
+ /// <remarks>
+ /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has
+ /// completed. If the criteria specified through the <paramref name="continuationOptions"/> parameter
+ /// are not met, the continuation task will be canceled instead of scheduled.
+ /// </remarks>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="continuationAction"/> argument is null.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentOutOfRangeException">
+ /// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see
+ /// cref="T:System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="scheduler"/> argument is null.
+ /// </exception>
+ /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see>
+ /// has already been disposed.
+ /// </exception>
+ [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable
+ public Task ContinueWith(Action<Task> continuationAction, CancellationToken cancellationToken,
+ TaskContinuationOptions continuationOptions, TaskScheduler scheduler)
+ {
+ StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
+ return ContinueWith(continuationAction, scheduler, cancellationToken, continuationOptions, ref stackMark);
+ }
+
+ // Same as the above overload, just with a stack mark parameter.
+ private Task ContinueWith(Action<Task> continuationAction, TaskScheduler scheduler,
+ CancellationToken cancellationToken, TaskContinuationOptions continuationOptions, ref StackCrawlMark stackMark)
+ {
+ // Throw on continuation with null action
+ if (continuationAction == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.continuationAction);
+ }
+
+ // Throw on continuation with null TaskScheduler
+ if (scheduler == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler);
+ }
+ Contract.EndContractBlock();
+
+ TaskCreationOptions creationOptions;
+ InternalTaskOptions internalOptions;
+ CreationOptionsFromContinuationOptions(continuationOptions, out creationOptions, out internalOptions);
+
+ Task continuationTask = new ContinuationTaskFromTask(
+ this, continuationAction, null,
+ creationOptions, internalOptions,
+ ref stackMark
+ );
+
+ // Register the continuation. If synchronous execution is requested, this may
+ // actually invoke the continuation before returning.
+ ContinueWithCore(continuationTask, scheduler, cancellationToken, continuationOptions);
+
+ return continuationTask;
+ }
+ #endregion
+
+ #region Action<Task, Object> continuation
+
+ /// <summary>
+ /// Creates a continuation that executes when the target <see cref="Task"/> completes.
+ /// </summary>
+ /// <param name="continuationAction">
+ /// An action to run when the <see cref="Task"/> completes. When run, the delegate will be
+ /// passed the completed task as and the caller-supplied state object as arguments.
+ /// </param>
+ /// <param name="state">An object representing data to be used by the continuation action.</param>
+ /// <returns>A new continuation <see cref="Task"/>.</returns>
+ /// <remarks>
+ /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has
+ /// completed, whether it completes due to running to completion successfully, faulting due to an
+ /// unhandled exception, or exiting out early due to being canceled.
+ /// </remarks>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="continuationAction"/> argument is null.
+ /// </exception>
+ [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable
+ public Task ContinueWith(Action<Task, Object> continuationAction, Object state)
+ {
+ StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
+ return ContinueWith(continuationAction, state, TaskScheduler.Current, default(CancellationToken), TaskContinuationOptions.None, ref stackMark);
+ }
+
+ /// <summary>
+ /// Creates a continuation that executes when the target <see cref="Task"/> completes.
+ /// </summary>
+ /// <param name="continuationAction">
+ /// An action to run when the <see cref="Task"/> completes. When run, the delegate will be
+ /// passed the completed task and the caller-supplied state object as arguments.
+ /// </param>
+ /// <param name="state">An object representing data to be used by the continuation action.</param>
+ /// <param name="cancellationToken"> The <see cref="CancellationToken"/> that will be assigned to the new continuation task.</param>
+ /// <returns>A new continuation <see cref="Task"/>.</returns>
+ /// <remarks>
+ /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has
+ /// completed, whether it completes due to running to completion successfully, faulting due to an
+ /// unhandled exception, or exiting out early due to being canceled.
+ /// </remarks>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="continuationAction"/> argument is null.
+ /// </exception>
+ /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see>
+ /// has already been disposed.
+ /// </exception>
+ [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable
+ public Task ContinueWith(Action<Task, Object> continuationAction, Object state, CancellationToken cancellationToken)
+ {
+ StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
+ return ContinueWith(continuationAction, state, TaskScheduler.Current, cancellationToken, TaskContinuationOptions.None, ref stackMark);
+ }
+
+ /// <summary>
+ /// Creates a continuation that executes when the target <see cref="Task"/> completes.
+ /// </summary>
+ /// <param name="continuationAction">
+ /// An action to run when the <see cref="Task"/> completes. When run, the delegate will be
+ /// passed the completed task and the caller-supplied state object as arguments.
+ /// </param>
+ /// <param name="state">An object representing data to be used by the continuation action.</param>
+ /// <param name="scheduler">
+ /// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its execution.
+ /// </param>
+ /// <returns>A new continuation <see cref="Task"/>.</returns>
+ /// <remarks>
+ /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has
+ /// completed, whether it completes due to running to completion successfully, faulting due to an
+ /// unhandled exception, or exiting out early due to being canceled.
+ /// </remarks>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="continuationAction"/> argument is null.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="scheduler"/> argument is null.
+ /// </exception>
+ [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable
+ public Task ContinueWith(Action<Task, Object> continuationAction, Object state, TaskScheduler scheduler)
+ {
+ StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
+ return ContinueWith(continuationAction, state, scheduler, default(CancellationToken), TaskContinuationOptions.None, ref stackMark);
+ }
+
+ /// <summary>
+ /// Creates a continuation that executes when the target <see cref="Task"/> completes.
+ /// </summary>
+ /// <param name="continuationAction">
+ /// An action to run when the <see cref="Task"/> completes. When run, the delegate will be
+ /// passed the completed task and the caller-supplied state object as arguments.
+ /// </param>
+ /// <param name="state">An object representing data to be used by the continuation action.</param>
+ /// <param name="continuationOptions">
+ /// Options for when the continuation is scheduled and how it behaves. This includes criteria, such
+ /// as <see
+ /// cref="System.Threading.Tasks.TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as
+ /// well as execution options, such as <see
+ /// cref="System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>.
+ /// </param>
+ /// <returns>A new continuation <see cref="Task"/>.</returns>
+ /// <remarks>
+ /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has
+ /// completed. If the continuation criteria specified through the <paramref
+ /// name="continuationOptions"/> parameter are not met, the continuation task will be canceled
+ /// instead of scheduled.
+ /// </remarks>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="continuationAction"/> argument is null.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentOutOfRangeException">
+ /// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see
+ /// cref="T:System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>.
+ /// </exception>
+ [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable
+ public Task ContinueWith(Action<Task, Object> continuationAction, Object state, TaskContinuationOptions continuationOptions)
+ {
+ StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
+ return ContinueWith(continuationAction, state, TaskScheduler.Current, default(CancellationToken), continuationOptions, ref stackMark);
+ }
+
+ /// <summary>
+ /// Creates a continuation that executes when the target <see cref="Task"/> completes.
+ /// </summary>
+ /// <param name="continuationAction">
+ /// An action to run when the <see cref="Task"/> completes. When run, the delegate will be
+ /// passed the completed task and the caller-supplied state object as arguments.
+ /// </param>
+ /// <param name="state">An object representing data to be used by the continuation action.</param>
+ /// <param name="continuationOptions">
+ /// Options for when the continuation is scheduled and how it behaves. This includes criteria, such
+ /// as <see
+ /// cref="System.Threading.Tasks.TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as
+ /// well as execution options, such as <see
+ /// cref="System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>.
+ /// </param>
+ /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new continuation task.</param>
+ /// <param name="scheduler">
+ /// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its
+ /// execution.
+ /// </param>
+ /// <returns>A new continuation <see cref="Task"/>.</returns>
+ /// <remarks>
+ /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has
+ /// completed. If the criteria specified through the <paramref name="continuationOptions"/> parameter
+ /// are not met, the continuation task will be canceled instead of scheduled.
+ /// </remarks>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="continuationAction"/> argument is null.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentOutOfRangeException">
+ /// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see
+ /// cref="T:System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="scheduler"/> argument is null.
+ /// </exception>
+ /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see>
+ /// has already been disposed.
+ /// </exception>
+ [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable
+ public Task ContinueWith(Action<Task, Object> continuationAction, Object state, CancellationToken cancellationToken,
+ TaskContinuationOptions continuationOptions, TaskScheduler scheduler)
+ {
+ StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
+ return ContinueWith(continuationAction, state, scheduler, cancellationToken, continuationOptions, ref stackMark);
+ }
+
+ // Same as the above overload, just with a stack mark parameter.
+ private Task ContinueWith(Action<Task, Object> continuationAction, Object state, TaskScheduler scheduler,
+ CancellationToken cancellationToken, TaskContinuationOptions continuationOptions, ref StackCrawlMark stackMark)
+ {
+ // Throw on continuation with null action
+ if (continuationAction == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.continuationAction);
+ }
+
+ // Throw on continuation with null TaskScheduler
+ if (scheduler == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler);
+ }
+ Contract.EndContractBlock();
+
+ TaskCreationOptions creationOptions;
+ InternalTaskOptions internalOptions;
+ CreationOptionsFromContinuationOptions(continuationOptions, out creationOptions, out internalOptions);
+
+ Task continuationTask = new ContinuationTaskFromTask(
+ this, continuationAction, state,
+ creationOptions, internalOptions,
+ ref stackMark
+ );
+
+ // Register the continuation. If synchronous execution is requested, this may
+ // actually invoke the continuation before returning.
+ ContinueWithCore(continuationTask, scheduler, cancellationToken, continuationOptions);
+
+ return continuationTask;
+ }
+
+ #endregion
+
+ #region Func<Task, TResult> continuation
+
+ /// <summary>
+ /// Creates a continuation that executes when the target <see cref="Task"/> completes.
+ /// </summary>
+ /// <typeparam name="TResult">
+ /// The type of the result produced by the continuation.
+ /// </typeparam>
+ /// <param name="continuationFunction">
+ /// A function to run when the <see cref="Task"/> completes. When run, the delegate will be
+ /// passed the completed task as an argument.
+ /// </param>
+ /// <returns>A new continuation <see cref="Task{TResult}"/>.</returns>
+ /// <remarks>
+ /// The returned <see cref="Task{TResult}"/> will not be scheduled for execution until the current task has
+ /// completed, whether it completes due to running to completion successfully, faulting due to an
+ /// unhandled exception, or exiting out early due to being canceled.
+ /// </remarks>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="continuationFunction"/> argument is null.
+ /// </exception>
+ [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable
+ public Task<TResult> ContinueWith<TResult>(Func<Task, TResult> continuationFunction)
+ {
+ StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
+ return ContinueWith<TResult>(continuationFunction, TaskScheduler.Current, default(CancellationToken),
+ TaskContinuationOptions.None, ref stackMark);
+ }
+
+
+ /// <summary>
+ /// Creates a continuation that executes when the target <see cref="Task"/> completes.
+ /// </summary>
+ /// <typeparam name="TResult">
+ /// The type of the result produced by the continuation.
+ /// </typeparam>
+ /// <param name="continuationFunction">
+ /// A function to run when the <see cref="Task"/> completes. When run, the delegate will be
+ /// passed the completed task as an argument.
+ /// </param>
+ /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new continuation task.</param>
+ /// <returns>A new continuation <see cref="Task{TResult}"/>.</returns>
+ /// <remarks>
+ /// The returned <see cref="Task{TResult}"/> will not be scheduled for execution until the current task has
+ /// completed, whether it completes due to running to completion successfully, faulting due to an
+ /// unhandled exception, or exiting out early due to being canceled.
+ /// </remarks>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="continuationFunction"/> argument is null.
+ /// </exception>
+ /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see>
+ /// has already been disposed.
+ /// </exception>
+ [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable
+ public Task<TResult> ContinueWith<TResult>(Func<Task, TResult> continuationFunction, CancellationToken cancellationToken)
+ {
+ StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
+ return ContinueWith<TResult>(continuationFunction, TaskScheduler.Current, cancellationToken, TaskContinuationOptions.None, ref stackMark);
+ }
+
+ /// <summary>
+ /// Creates a continuation that executes when the target <see cref="Task"/> completes.
+ /// </summary>
+ /// <typeparam name="TResult">
+ /// The type of the result produced by the continuation.
+ /// </typeparam>
+ /// <param name="continuationFunction">
+ /// A function to run when the <see cref="Task"/> completes. When run, the delegate will be
+ /// passed the completed task as an argument.
+ /// </param>
+ /// <param name="scheduler">
+ /// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its execution.
+ /// </param>
+ /// <returns>A new continuation <see cref="Task{TResult}"/>.</returns>
+ /// <remarks>
+ /// The returned <see cref="Task{TResult}"/> will not be scheduled for execution until the current task has
+ /// completed, whether it completes due to running to completion successfully, faulting due to an
+ /// unhandled exception, or exiting out early due to being canceled.
+ /// </remarks>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="continuationFunction"/> argument is null.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="scheduler"/> argument is null.
+ /// </exception>
+ [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable
+ public Task<TResult> ContinueWith<TResult>(Func<Task, TResult> continuationFunction, TaskScheduler scheduler)
+ {
+ StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
+ return ContinueWith<TResult>(continuationFunction, scheduler, default(CancellationToken), TaskContinuationOptions.None, ref stackMark);
+ }
+
+ /// <summary>
+ /// Creates a continuation that executes when the target <see cref="Task"/> completes.
+ /// </summary>
+ /// <typeparam name="TResult">
+ /// The type of the result produced by the continuation.
+ /// </typeparam>
+ /// <param name="continuationFunction">
+ /// A function to run when the <see cref="Task"/> completes. When run, the delegate will be
+ /// passed the completed task as an argument.
+ /// </param>
+ /// <param name="continuationOptions">
+ /// Options for when the continuation is scheduled and how it behaves. This includes criteria, such
+ /// as <see
+ /// cref="System.Threading.Tasks.TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as
+ /// well as execution options, such as <see
+ /// cref="System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>.
+ /// </param>
+ /// <returns>A new continuation <see cref="Task{TResult}"/>.</returns>
+ /// <remarks>
+ /// The returned <see cref="Task{TResult}"/> will not be scheduled for execution until the current task has
+ /// completed. If the continuation criteria specified through the <paramref
+ /// name="continuationOptions"/> parameter are not met, the continuation task will be canceled
+ /// instead of scheduled.
+ /// </remarks>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="continuationFunction"/> argument is null.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentOutOfRangeException">
+ /// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see
+ /// cref="T:System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>.
+ /// </exception>
+ [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable
+ public Task<TResult> ContinueWith<TResult>(Func<Task, TResult> continuationFunction, TaskContinuationOptions continuationOptions)
+ {
+ StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
+ return ContinueWith<TResult>(continuationFunction, TaskScheduler.Current, default(CancellationToken), continuationOptions, ref stackMark);
+ }
+
+ /// <summary>
+ /// Creates a continuation that executes when the target <see cref="Task"/> completes.
+ /// </summary>
+ /// <typeparam name="TResult">
+ /// The type of the result produced by the continuation.
+ /// </typeparam>
+ /// <param name="continuationFunction">
+ /// A function to run when the <see cref="Task"/> completes. When run, the delegate will be
+ /// passed the completed task as an argument.
+ /// </param>
+ /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new continuation task.</param>
+ /// <param name="continuationOptions">
+ /// Options for when the continuation is scheduled and how it behaves. This includes criteria, such
+ /// as <see
+ /// cref="System.Threading.Tasks.TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as
+ /// well as execution options, such as <see
+ /// cref="System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>.
+ /// </param>
+ /// <param name="scheduler">
+ /// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its
+ /// execution.
+ /// </param>
+ /// <returns>A new continuation <see cref="Task{TResult}"/>.</returns>
+ /// <remarks>
+ /// The returned <see cref="Task{TResult}"/> will not be scheduled for execution until the current task has
+ /// completed. If the criteria specified through the <paramref name="continuationOptions"/> parameter
+ /// are not met, the continuation task will be canceled instead of scheduled.
+ /// </remarks>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="continuationFunction"/> argument is null.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentOutOfRangeException">
+ /// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see
+ /// cref="T:System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="scheduler"/> argument is null.
+ /// </exception>
+ /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see>
+ /// has already been disposed.
+ /// </exception>
+ [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable
+ public Task<TResult> ContinueWith<TResult>(Func<Task, TResult> continuationFunction, CancellationToken cancellationToken,
+ TaskContinuationOptions continuationOptions, TaskScheduler scheduler)
+ {
+ StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
+ return ContinueWith<TResult>(continuationFunction, scheduler, cancellationToken, continuationOptions, ref stackMark);
+ }
+
+ // Same as the above overload, just with a stack mark parameter.
+ private Task<TResult> ContinueWith<TResult>(Func<Task, TResult> continuationFunction, TaskScheduler scheduler,
+ CancellationToken cancellationToken, TaskContinuationOptions continuationOptions, ref StackCrawlMark stackMark)
+ {
+ // Throw on continuation with null function
+ if (continuationFunction == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.continuationFunction);
+ }
+
+ // Throw on continuation with null task scheduler
+ if (scheduler == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler);
+ }
+ Contract.EndContractBlock();
+
+ TaskCreationOptions creationOptions;
+ InternalTaskOptions internalOptions;
+ CreationOptionsFromContinuationOptions(continuationOptions, out creationOptions, out internalOptions);
+
+ Task<TResult> continuationTask = new ContinuationResultTaskFromTask<TResult>(
+ this, continuationFunction, null,
+ creationOptions, internalOptions,
+ ref stackMark
+ );
+
+ // Register the continuation. If synchronous execution is requested, this may
+ // actually invoke the continuation before returning.
+ ContinueWithCore(continuationTask, scheduler, cancellationToken, continuationOptions);
+
+ return continuationTask;
+ }
+ #endregion
+
+ #region Func<Task, Object, TResult> continuation
+
+ /// <summary>
+ /// Creates a continuation that executes when the target <see cref="Task"/> completes.
+ /// </summary>
+ /// <typeparam name="TResult">
+ /// The type of the result produced by the continuation.
+ /// </typeparam>
+ /// <param name="continuationFunction">
+ /// A function to run when the <see cref="Task"/> completes. When run, the delegate will be
+ /// passed the completed task and the caller-supplied state object as arguments.
+ /// </param>
+ /// <param name="state">An object representing data to be used by the continuation function.</param>
+ /// <returns>A new continuation <see cref="Task{TResult}"/>.</returns>
+ /// <remarks>
+ /// The returned <see cref="Task{TResult}"/> will not be scheduled for execution until the current task has
+ /// completed, whether it completes due to running to completion successfully, faulting due to an
+ /// unhandled exception, or exiting out early due to being canceled.
+ /// </remarks>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="continuationFunction"/> argument is null.
+ /// </exception>
+ [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable
+ public Task<TResult> ContinueWith<TResult>(Func<Task, Object, TResult> continuationFunction, Object state)
+ {
+ StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
+ return ContinueWith<TResult>(continuationFunction, state, TaskScheduler.Current, default(CancellationToken),
+ TaskContinuationOptions.None, ref stackMark);
+ }
+
+
+ /// <summary>
+ /// Creates a continuation that executes when the target <see cref="Task"/> completes.
+ /// </summary>
+ /// <typeparam name="TResult">
+ /// The type of the result produced by the continuation.
+ /// </typeparam>
+ /// <param name="continuationFunction">
+ /// A function to run when the <see cref="Task"/> completes. When run, the delegate will be
+ /// passed the completed task and the caller-supplied state object as arguments.
+ /// </param>
+ /// <param name="state">An object representing data to be used by the continuation function.</param>
+ /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new continuation task.</param>
+ /// <returns>A new continuation <see cref="Task{TResult}"/>.</returns>
+ /// <remarks>
+ /// The returned <see cref="Task{TResult}"/> will not be scheduled for execution until the current task has
+ /// completed, whether it completes due to running to completion successfully, faulting due to an
+ /// unhandled exception, or exiting out early due to being canceled.
+ /// </remarks>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="continuationFunction"/> argument is null.
+ /// </exception>
+ /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see>
+ /// has already been disposed.
+ /// </exception>
+ [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable
+ public Task<TResult> ContinueWith<TResult>(Func<Task, Object, TResult> continuationFunction, Object state, CancellationToken cancellationToken)
+ {
+ StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
+ return ContinueWith<TResult>(continuationFunction, state, TaskScheduler.Current, cancellationToken, TaskContinuationOptions.None, ref stackMark);
+ }
+
+ /// <summary>
+ /// Creates a continuation that executes when the target <see cref="Task"/> completes.
+ /// </summary>
+ /// <typeparam name="TResult">
+ /// The type of the result produced by the continuation.
+ /// </typeparam>
+ /// <param name="continuationFunction">
+ /// A function to run when the <see cref="Task"/> completes. When run, the delegate will be
+ /// passed the completed task and the caller-supplied state object as arguments.
+ /// </param>
+ /// <param name="state">An object representing data to be used by the continuation function.</param>
+ /// <param name="scheduler">
+ /// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its execution.
+ /// </param>
+ /// <returns>A new continuation <see cref="Task{TResult}"/>.</returns>
+ /// <remarks>
+ /// The returned <see cref="Task{TResult}"/> will not be scheduled for execution until the current task has
+ /// completed, whether it completes due to running to completion successfully, faulting due to an
+ /// unhandled exception, or exiting out early due to being canceled.
+ /// </remarks>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="continuationFunction"/> argument is null.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="scheduler"/> argument is null.
+ /// </exception>
+ [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable
+ public Task<TResult> ContinueWith<TResult>(Func<Task, Object, TResult> continuationFunction, Object state, TaskScheduler scheduler)
+ {
+ StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
+ return ContinueWith<TResult>(continuationFunction, state, scheduler, default(CancellationToken), TaskContinuationOptions.None, ref stackMark);
+ }
+
+ /// <summary>
+ /// Creates a continuation that executes when the target <see cref="Task"/> completes.
+ /// </summary>
+ /// <typeparam name="TResult">
+ /// The type of the result produced by the continuation.
+ /// </typeparam>
+ /// <param name="continuationFunction">
+ /// A function to run when the <see cref="Task"/> completes. When run, the delegate will be
+ /// passed the completed task and the caller-supplied state object as arguments.
+ /// </param>
+ /// <param name="state">An object representing data to be used by the continuation function.</param>
+ /// <param name="continuationOptions">
+ /// Options for when the continuation is scheduled and how it behaves. This includes criteria, such
+ /// as <see
+ /// cref="System.Threading.Tasks.TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as
+ /// well as execution options, such as <see
+ /// cref="System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>.
+ /// </param>
+ /// <returns>A new continuation <see cref="Task{TResult}"/>.</returns>
+ /// <remarks>
+ /// The returned <see cref="Task{TResult}"/> will not be scheduled for execution until the current task has
+ /// completed. If the continuation criteria specified through the <paramref
+ /// name="continuationOptions"/> parameter are not met, the continuation task will be canceled
+ /// instead of scheduled.
+ /// </remarks>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="continuationFunction"/> argument is null.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentOutOfRangeException">
+ /// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see
+ /// cref="T:System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>.
+ /// </exception>
+ [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable
+ public Task<TResult> ContinueWith<TResult>(Func<Task, Object, TResult> continuationFunction, Object state, TaskContinuationOptions continuationOptions)
+ {
+ StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
+ return ContinueWith<TResult>(continuationFunction, state, TaskScheduler.Current, default(CancellationToken), continuationOptions, ref stackMark);
+ }
+
+ /// <summary>
+ /// Creates a continuation that executes when the target <see cref="Task"/> completes.
+ /// </summary>
+ /// <typeparam name="TResult">
+ /// The type of the result produced by the continuation.
+ /// </typeparam>
+ /// <param name="continuationFunction">
+ /// A function to run when the <see cref="Task"/> completes. When run, the delegate will be
+ /// passed the completed task and the caller-supplied state object as arguments.
+ /// </param>
+ /// <param name="state">An object representing data to be used by the continuation function.</param>
+ /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new continuation task.</param>
+ /// <param name="continuationOptions">
+ /// Options for when the continuation is scheduled and how it behaves. This includes criteria, such
+ /// as <see
+ /// cref="System.Threading.Tasks.TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as
+ /// well as execution options, such as <see
+ /// cref="System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>.
+ /// </param>
+ /// <param name="scheduler">
+ /// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its
+ /// execution.
+ /// </param>
+ /// <returns>A new continuation <see cref="Task{TResult}"/>.</returns>
+ /// <remarks>
+ /// The returned <see cref="Task{TResult}"/> will not be scheduled for execution until the current task has
+ /// completed. If the criteria specified through the <paramref name="continuationOptions"/> parameter
+ /// are not met, the continuation task will be canceled instead of scheduled.
+ /// </remarks>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="continuationFunction"/> argument is null.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentOutOfRangeException">
+ /// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see
+ /// cref="T:System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="scheduler"/> argument is null.
+ /// </exception>
+ /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see>
+ /// has already been disposed.
+ /// </exception>
+ [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable
+ public Task<TResult> ContinueWith<TResult>(Func<Task, Object, TResult> continuationFunction, Object state, CancellationToken cancellationToken,
+ TaskContinuationOptions continuationOptions, TaskScheduler scheduler)
+ {
+ StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
+ return ContinueWith<TResult>(continuationFunction, state, scheduler, cancellationToken, continuationOptions, ref stackMark);
+ }
+
+ // Same as the above overload, just with a stack mark parameter.
+ private Task<TResult> ContinueWith<TResult>(Func<Task, Object, TResult> continuationFunction, Object state, TaskScheduler scheduler,
+ CancellationToken cancellationToken, TaskContinuationOptions continuationOptions, ref StackCrawlMark stackMark)
+ {
+ // Throw on continuation with null function
+ if (continuationFunction == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.continuationFunction);
+ }
+
+ // Throw on continuation with null task scheduler
+ if (scheduler == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler);
+ }
+ Contract.EndContractBlock();
+
+ TaskCreationOptions creationOptions;
+ InternalTaskOptions internalOptions;
+ CreationOptionsFromContinuationOptions(continuationOptions, out creationOptions, out internalOptions);
+
+ Task<TResult> continuationTask = new ContinuationResultTaskFromTask<TResult>(
+ this, continuationFunction, state,
+ creationOptions, internalOptions,
+ ref stackMark
+ );
+
+ // Register the continuation. If synchronous execution is requested, this may
+ // actually invoke the continuation before returning.
+ ContinueWithCore(continuationTask, scheduler, cancellationToken, continuationOptions);
+
+ return continuationTask;
+ }
+ #endregion
+
+ /// <summary>
+ /// Converts TaskContinuationOptions to TaskCreationOptions, and also does
+ /// some validity checking along the way.
+ /// </summary>
+ /// <param name="continuationOptions">Incoming TaskContinuationOptions</param>
+ /// <param name="creationOptions">Outgoing TaskCreationOptions</param>
+ /// <param name="internalOptions">Outgoing InternalTaskOptions</param>
+ internal static void CreationOptionsFromContinuationOptions(
+ TaskContinuationOptions continuationOptions,
+ out TaskCreationOptions creationOptions,
+ out InternalTaskOptions internalOptions)
+ {
+ // This is used a couple of times below
+ TaskContinuationOptions NotOnAnything =
+ TaskContinuationOptions.NotOnCanceled |
+ TaskContinuationOptions.NotOnFaulted |
+ TaskContinuationOptions.NotOnRanToCompletion;
+
+ TaskContinuationOptions creationOptionsMask =
+ TaskContinuationOptions.PreferFairness |
+ TaskContinuationOptions.LongRunning |
+ TaskContinuationOptions.DenyChildAttach |
+ TaskContinuationOptions.HideScheduler |
+ TaskContinuationOptions.AttachedToParent|
+ TaskContinuationOptions.RunContinuationsAsynchronously;
+
+ // Check that LongRunning and ExecuteSynchronously are not specified together
+ TaskContinuationOptions illegalMask = TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.LongRunning;
+ if ((continuationOptions & illegalMask) == illegalMask)
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.continuationOptions, ExceptionResource.Task_ContinueWith_ESandLR);
+ }
+
+ // Check that no illegal options were specified
+ if ((continuationOptions &
+ ~(creationOptionsMask | NotOnAnything |
+ TaskContinuationOptions.LazyCancellation | TaskContinuationOptions.ExecuteSynchronously)) != 0)
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.continuationOptions);
+ }
+
+ // Check that we didn't specify "not on anything"
+ if ((continuationOptions & NotOnAnything) == NotOnAnything)
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.continuationOptions, ExceptionResource.Task_ContinueWith_NotOnAnything);
+ }
+
+ // This passes over all but LazyCancellation, which has no representation in TaskCreationOptions
+ creationOptions = (TaskCreationOptions)(continuationOptions & creationOptionsMask);
+
+ // internalOptions has at least ContinuationTask ...
+ internalOptions = InternalTaskOptions.ContinuationTask;
+
+ // ... and possibly LazyCancellation
+ if ((continuationOptions & TaskContinuationOptions.LazyCancellation) != 0)
+ internalOptions |= InternalTaskOptions.LazyCancellation;
+ }
+
+
+ /// <summary>
+ /// Registers the continuation and possibly runs it (if the task is already finished).
+ /// </summary>
+ /// <param name="continuationTask">The continuation task itself.</param>
+ /// <param name="scheduler">TaskScheduler with which to associate continuation task.</param>
+ /// <param name="options">Restrictions on when the continuation becomes active.</param>
+ internal void ContinueWithCore(Task continuationTask,
+ TaskScheduler scheduler,
+ CancellationToken cancellationToken,
+ TaskContinuationOptions options)
+ {
+ Contract.Requires(continuationTask != null, "Task.ContinueWithCore(): null continuationTask");
+ Contract.Requires(scheduler != null, "Task.ContinueWithCore(): null scheduler");
+ Contract.Requires(!continuationTask.IsCompleted, "Did not expect continuationTask to be completed");
+
+ // Create a TaskContinuation
+ TaskContinuation continuation = new StandardTaskContinuation(continuationTask, options, scheduler);
+
+ // If cancellationToken is cancellable, then assign it.
+ if (cancellationToken.CanBeCanceled)
+ {
+ if (IsCompleted || cancellationToken.IsCancellationRequested)
+ {
+ // If the antecedent has completed, then we will not be queuing up
+ // the continuation in the antecedent's continuation list. Likewise,
+ // if the cancellationToken has been canceled, continuationTask will
+ // be completed in the AssignCancellationToken call below, and there
+ // is no need to queue the continuation to the antecedent's continuation
+ // list. In either of these two cases, we will pass "null" for the antecedent,
+ // meaning "the cancellation callback should not attempt to remove the
+ // continuation from its antecedent's continuation list".
+ continuationTask.AssignCancellationToken(cancellationToken, null, null);
+ }
+ else
+ {
+ // The antecedent is not yet complete, so there is a pretty good chance
+ // that the continuation will be queued up in the antecedent. Assign the
+ // cancellation token with information about the antecedent, so that the
+ // continuation can be dequeued upon the signalling of the token.
+ //
+ // It's possible that the antecedent completes before the call to AddTaskContinuation,
+ // and that is a benign race condition. It just means that the cancellation will result in
+ // a futile search of the antecedent's continuation list.
+ continuationTask.AssignCancellationToken(cancellationToken, this, continuation);
+ }
+ }
+
+ // In the case of a pre-canceled token, continuationTask will have been completed
+ // in a Canceled state by now. If such is the case, there is no need to go through
+ // the motions of queuing up the continuation for eventual execution.
+ if (!continuationTask.IsCompleted)
+ {
+ // We need additional correlation produced here to ensure that at least the continuation
+ // code will be correlatable to the currrent activity that initiated "this" task:
+ // . when the antecendent ("this") is a promise we have very little control over where
+ // the code for the promise will run (e.g. it can be a task from a user provided
+ // TaskCompletionSource or from a classic Begin/End async operation); this user or
+ // system code will likely not have stamped an activity id on the thread, so there's
+ // generally no easy correlation that can be provided between the current activity
+ // and the promise. Also the continuation code may run practically on any thread.
+ // Since there may be no correlation between the current activity and the TCS's task
+ // activity, we ensure we at least create a correlation from the current activity to
+ // the continuation that runs when the promise completes.
+ if ((this.Options & (TaskCreationOptions)InternalTaskOptions.PromiseTask) != 0 &&
+ !(this is ITaskCompletionAction))
+ {
+ var etwLog = TplEtwProvider.Log;
+ if (etwLog.IsEnabled())
+ {
+ etwLog.AwaitTaskContinuationScheduled(TaskScheduler.Current.Id, Task.CurrentId ?? 0, continuationTask.Id);
+ }
+ }
+
+ // Attempt to enqueue the continuation
+ bool continuationQueued = AddTaskContinuation(continuation, addBeforeOthers: false);
+
+ // If the continuation was not queued (because the task completed), then run it now.
+ if (!continuationQueued) continuation.Run(this, bCanInlineContinuationTask: true);
+ }
+ }
+ #endregion
+
+ // Adds a lightweight completion action to a task. This is similar to a continuation
+ // task except that it is stored as an action, and thus does not require the allocation/
+ // execution resources of a continuation task.
+ //
+ // Used internally by ContinueWhenAll() and ContinueWhenAny().
+ internal void AddCompletionAction(ITaskCompletionAction action)
+ {
+ AddCompletionAction(action, addBeforeOthers: false);
+ }
+
+ private void AddCompletionAction(ITaskCompletionAction action, bool addBeforeOthers)
+ {
+ if (!AddTaskContinuation(action, addBeforeOthers))
+ action.Invoke(this); // run the action directly if we failed to queue the continuation (i.e., the task completed)
+ }
+
+ // Support method for AddTaskContinuation that takes care of multi-continuation logic.
+ // Returns true if and only if the continuation was successfully queued.
+ // THIS METHOD ASSUMES THAT m_continuationObject IS NOT NULL. That case was taken
+ // care of in the calling method, AddTaskContinuation().
+ private bool AddTaskContinuationComplex(object tc, bool addBeforeOthers)
+ {
+ Contract.Requires(tc != null, "Expected non-null tc object in AddTaskContinuationComplex");
+
+ object oldValue = m_continuationObject;
+
+ // Logic for the case where we were previously storing a single continuation
+ if ((oldValue != s_taskCompletionSentinel) && (!(oldValue is List<object>)))
+ {
+ // Construct a new TaskContinuation list
+ List<object> newList = new List<object>();
+
+ // Add in the old single value
+ newList.Add(oldValue);
+
+ // Now CAS in the new list
+ Interlocked.CompareExchange(ref m_continuationObject, newList, oldValue);
+
+ // We might be racing against another thread converting the single into
+ // a list, or we might be racing against task completion, so resample "list"
+ // below.
+ }
+
+ // m_continuationObject is guaranteed at this point to be either a List or
+ // s_taskCompletionSentinel.
+ List<object> list = m_continuationObject as List<object>;
+ Contract.Assert((list != null) || (m_continuationObject == s_taskCompletionSentinel),
+ "Expected m_continuationObject to be list or sentinel");
+
+ // If list is null, it can only mean that s_taskCompletionSentinel has been exchanged
+ // into m_continuationObject. Thus, the task has completed and we should return false
+ // from this method, as we will not be queuing up the continuation.
+ if (list != null)
+ {
+ lock (list)
+ {
+ // It is possible for the task to complete right after we snap the copy of
+ // the list. If so, then fall through and return false without queuing the
+ // continuation.
+ if (m_continuationObject != s_taskCompletionSentinel)
+ {
+ // Before growing the list we remove possible null entries that are the
+ // result from RemoveContinuations()
+ if (list.Count == list.Capacity)
+ {
+ list.RemoveAll(s_IsTaskContinuationNullPredicate);
+ }
+
+ if (addBeforeOthers)
+ list.Insert(0, tc);
+ else
+ list.Add(tc);
+
+ return true; // continuation successfully queued, so return true.
+ }
+ }
+ }
+
+ // We didn't succeed in queuing the continuation, so return false.
+ return false;
+ }
+
+ // Record a continuation task or action.
+ // Return true if and only if we successfully queued a continuation.
+ private bool AddTaskContinuation(object tc, bool addBeforeOthers)
+ {
+ Contract.Requires(tc != null);
+
+ // Make sure that, if someone calls ContinueWith() right after waiting for the predecessor to complete,
+ // we don't queue up a continuation.
+ if (IsCompleted) return false;
+
+ // Try to just jam tc into m_continuationObject
+ if ((m_continuationObject != null) || (Interlocked.CompareExchange(ref m_continuationObject, tc, null) != null))
+ {
+ // If we get here, it means that we failed to CAS tc into m_continuationObject.
+ // Therefore, we must go the more complicated route.
+ return AddTaskContinuationComplex(tc, addBeforeOthers);
+ }
+ else return true;
+ }
+
+ // Removes a continuation task from m_continuations
+ internal void RemoveContinuation(object continuationObject) // could be TaskContinuation or Action<Task>
+ {
+ // We need to snap a local reference to m_continuations since reading a volatile object is more costly.
+ // Also to prevent the value to be changed as result of a race condition with another method.
+ object continuationsLocalRef = m_continuationObject;
+
+ // Task is completed. Nothing to do here.
+ if (continuationsLocalRef == s_taskCompletionSentinel) return;
+
+ List<object> continuationsLocalListRef = continuationsLocalRef as List<object>;
+
+ if (continuationsLocalListRef == null)
+ {
+ // 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.
+ // Note we cannot go back to a null state, since it will mess up the AddTaskContinuation logic.
+ if (Interlocked.CompareExchange(ref m_continuationObject, new List<object>(), continuationObject) != continuationObject)
+ {
+ // If we fail it means that either AddContinuationComplex won the race condition and m_continuationObject is now a List
+ // that contains the element we want to remove. Or FinishContinuations set the s_taskCompletionSentinel.
+ // So we should try to get a list one more time
+ continuationsLocalListRef = m_continuationObject as List<object>;
+ }
+ else
+ {
+ // Exchange was successful so we can skip the last comparison
+ return;
+ }
+ }
+
+ // if continuationsLocalRef == null it means s_taskCompletionSentinel has been set already and there is nothing else to do.
+ if (continuationsLocalListRef != null)
+ {
+ lock (continuationsLocalListRef)
+ {
+ // There is a small chance that this task completed since we took a local snapshot into
+ // continuationsLocalRef. In that case, just return; we don't want to be manipulating the
+ // continuation list as it is being processed.
+ if (m_continuationObject == s_taskCompletionSentinel) return;
+
+ // Find continuationObject in the continuation list
+ int index = continuationsLocalListRef.IndexOf(continuationObject);
+
+ if (index != -1)
+ {
+ // null out that TaskContinuation entry, which will be interpreted as "to be cleaned up"
+ continuationsLocalListRef[index] = null;
+
+ }
+ }
+ }
+ }
+
+ // statically allocated delegate for the RemoveAll expression in RemoveContinuations() and AddContinuationComplex()
+ private readonly static Predicate<object> s_IsTaskContinuationNullPredicate =
+ new Predicate<object>((tc) => { return (tc == null); });
+
+
+ //
+ // Wait methods
+ //
+
+ /// <summary>
+ /// Waits for all of the provided <see cref="Task"/> objects to complete execution.
+ /// </summary>
+ /// <param name="tasks">
+ /// An array of <see cref="Task"/> instances on which to wait.
+ /// </param>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="tasks"/> argument is null.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="tasks"/> argument contains a null element.
+ /// </exception>
+ /// <exception cref="T:System.AggregateException">
+ /// At least one of the <see cref="Task"/> instances was canceled -or- an exception was thrown during
+ /// the execution of at least one of the <see cref="Task"/> instances.
+ /// </exception>
+ [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
+ public static void WaitAll(params Task[] tasks)
+ {
+#if DEBUG
+ bool waitResult =
+#endif
+ WaitAll(tasks, Timeout.Infinite);
+
+#if DEBUG
+ Contract.Assert(waitResult, "expected wait to succeed");
+#endif
+ }
+
+ /// <summary>
+ /// Waits for all of the provided <see cref="Task"/> objects to complete execution.
+ /// </summary>
+ /// <returns>
+ /// true if all of the <see cref="Task"/> instances completed execution within the allotted time;
+ /// otherwise, false.
+ /// </returns>
+ /// <param name="tasks">
+ /// An array of <see cref="Task"/> instances on which to wait.
+ /// </param>
+ /// <param name="timeout">
+ /// A <see cref="System.TimeSpan"/> that represents the number of milliseconds to wait, or a <see
+ /// cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely.
+ /// </param>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="tasks"/> argument is null.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentException">
+ /// The <paramref name="tasks"/> argument contains a null element.
+ /// </exception>
+ /// <exception cref="T:System.AggregateException">
+ /// At least one of the <see cref="Task"/> instances was canceled -or- an exception was thrown during
+ /// the execution of at least one of the <see cref="Task"/> instances.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentOutOfRangeException">
+ /// <paramref name="timeout"/> is a negative number other than -1 milliseconds, which represents an
+ /// infinite time-out -or- timeout is greater than
+ /// <see cref="System.Int32.MaxValue"/>.
+ /// </exception>
+ [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
+ public static bool WaitAll(Task[] tasks, TimeSpan timeout)
+ {
+ long totalMilliseconds = (long)timeout.TotalMilliseconds;
+ if (totalMilliseconds < -1 || totalMilliseconds > Int32.MaxValue)
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.timeout);
+ }
+
+ return WaitAll(tasks, (int)totalMilliseconds);
+
+ }
+
+ /// <summary>
+ /// Waits for all of the provided <see cref="Task"/> objects to complete execution.
+ /// </summary>
+ /// <returns>
+ /// true if all of the <see cref="Task"/> instances completed execution within the allotted time;
+ /// otherwise, false.
+ /// </returns>
+ /// <param name="millisecondsTimeout">
+ /// The number of milliseconds to wait, or <see cref="System.Threading.Timeout.Infinite"/> (-1) to
+ /// wait indefinitely.</param>
+ /// <param name="tasks">An array of <see cref="Task"/> instances on which to wait.
+ /// </param>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="tasks"/> argument is null.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentException">
+ /// The <paramref name="tasks"/> argument contains a null element.
+ /// </exception>
+ /// <exception cref="T:System.AggregateException">
+ /// At least one of the <see cref="Task"/> instances was canceled -or- an exception was thrown during
+ /// the execution of at least one of the <see cref="Task"/> instances.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentOutOfRangeException">
+ /// <paramref name="millisecondsTimeout"/> is a negative number other than -1, which represents an
+ /// infinite time-out.
+ /// </exception>
+ [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
+ public static bool WaitAll(Task[] tasks, int millisecondsTimeout)
+ {
+ return WaitAll(tasks, millisecondsTimeout, default(CancellationToken));
+ }
+
+ /// <summary>
+ /// Waits for all of the provided <see cref="Task"/> objects to complete execution.
+ /// </summary>
+ /// <returns>
+ /// true if all of the <see cref="Task"/> instances completed execution within the allotted time;
+ /// otherwise, false.
+ /// </returns>
+ /// <param name="tasks">
+ /// An array of <see cref="Task"/> instances on which to wait.
+ /// </param>
+ /// <param name="cancellationToken">
+ /// A <see cref="CancellationToken"/> to observe while waiting for the tasks to complete.
+ /// </param>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="tasks"/> argument is null.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentException">
+ /// The <paramref name="tasks"/> argument contains a null element.
+ /// </exception>
+ /// <exception cref="T:System.AggregateException">
+ /// At least one of the <see cref="Task"/> instances was canceled -or- an exception was thrown during
+ /// the execution of at least one of the <see cref="Task"/> instances.
+ /// </exception>
+ /// <exception cref="T:System.OperationCanceledException">
+ /// The <paramref name="cancellationToken"/> was canceled.
+ /// </exception>
+ [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
+ public static void WaitAll(Task[] tasks, CancellationToken cancellationToken)
+ {
+ WaitAll(tasks, Timeout.Infinite, cancellationToken);
+ }
+
+ /// <summary>
+ /// Waits for all of the provided <see cref="Task"/> objects to complete execution.
+ /// </summary>
+ /// <returns>
+ /// true if all of the <see cref="Task"/> instances completed execution within the allotted time;
+ /// otherwise, false.
+ /// </returns>
+ /// <param name="tasks">
+ /// An array of <see cref="Task"/> instances on which to wait.
+ /// </param>
+ /// <param name="millisecondsTimeout">
+ /// The number of milliseconds to wait, or <see cref="System.Threading.Timeout.Infinite"/> (-1) to
+ /// wait indefinitely.
+ /// </param>
+ /// <param name="cancellationToken">
+ /// A <see cref="CancellationToken"/> to observe while waiting for the tasks to complete.
+ /// </param>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="tasks"/> argument is null.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentException">
+ /// The <paramref name="tasks"/> argument contains a null element.
+ /// </exception>
+ /// <exception cref="T:System.AggregateException">
+ /// At least one of the <see cref="Task"/> instances was canceled -or- an exception was thrown during
+ /// the execution of at least one of the <see cref="Task"/> instances.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentOutOfRangeException">
+ /// <paramref name="millisecondsTimeout"/> is a negative number other than -1, which represents an
+ /// infinite time-out.
+ /// </exception>
+ /// <exception cref="T:System.OperationCanceledException">
+ /// The <paramref name="cancellationToken"/> was canceled.
+ /// </exception>
+ [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
+ public static bool WaitAll(Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken)
+ {
+ if (tasks == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks);
+ }
+ if (millisecondsTimeout < -1)
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.millisecondsTimeout);
+ }
+ Contract.EndContractBlock();
+
+ cancellationToken.ThrowIfCancellationRequested(); // early check before we make any allocations
+
+ //
+ // In this WaitAll() implementation we have 2 alternate code paths for a task to be handled:
+ // CODEPATH1: skip an already completed task, CODEPATH2: actually wait on tasks
+ // We make sure that the exception behavior of Task.Wait() is replicated the same for tasks handled in either of these codepaths
+ //
+
+ List<Exception> exceptions = null;
+ List<Task> waitedOnTaskList = null;
+ List<Task> notificationTasks = null;
+
+ // If any of the waited-upon tasks end as Faulted or Canceled, set these to true.
+ bool exceptionSeen = false, cancellationSeen = false;
+
+ bool returnValue = true;
+
+ // Collects incomplete tasks in "waitedOnTaskList"
+ for (int i = tasks.Length - 1; i >= 0; i--)
+ {
+ Task task = tasks[i];
+
+ if (task == null)
+ {
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Task_WaitMulti_NullTask, ExceptionArgument.tasks);
+ }
+
+ bool taskIsCompleted = task.IsCompleted;
+ if (!taskIsCompleted)
+ {
+ // try inlining the task only if we have an infinite timeout and an empty cancellation token
+ if (millisecondsTimeout != Timeout.Infinite || cancellationToken.CanBeCanceled)
+ {
+ // We either didn't attempt inline execution because we had a non-infinite timeout or we had a cancellable token.
+ // In all cases we need to do a full wait on the task (=> add its event into the list.)
+ AddToList(task, ref waitedOnTaskList, initSize: tasks.Length);
+ }
+ else
+ {
+ // We are eligible for inlining. If it doesn't work, we'll do a full wait.
+ taskIsCompleted = task.WrappedTryRunInline() && task.IsCompleted; // A successful TryRunInline doesn't guarantee completion
+ if (!taskIsCompleted) AddToList(task, ref waitedOnTaskList, initSize: tasks.Length);
+ }
+ }
+
+ if (taskIsCompleted)
+ {
+ if (task.IsFaulted) exceptionSeen = true;
+ else if (task.IsCanceled) cancellationSeen = true;
+ if (task.IsWaitNotificationEnabled) AddToList(task, ref notificationTasks, initSize: 1);
+ }
+ }
+
+ if (waitedOnTaskList != null)
+ {
+ // Block waiting for the tasks to complete.
+ returnValue = WaitAllBlockingCore(waitedOnTaskList, millisecondsTimeout, cancellationToken);
+
+ // If the wait didn't time out, ensure exceptions are propagated, and if a debugger is
+ // attached and one of these tasks requires it, that we notify the debugger of a wait completion.
+ if (returnValue)
+ {
+ // Add any exceptions for this task to the collection, and if it's wait
+ // notification bit is set, store it to operate on at the end.
+ foreach (var task in waitedOnTaskList)
+ {
+ if (task.IsFaulted) exceptionSeen = true;
+ else if (task.IsCanceled) cancellationSeen = true;
+ if (task.IsWaitNotificationEnabled) AddToList(task, ref notificationTasks, initSize: 1);
+ }
+ }
+
+ // We need to prevent the tasks array from being GC'ed until we come out of the wait.
+ // This is necessary so that the Parallel Debugger can traverse it during the long wait and
+ // deduce waiter/waitee relationships
+ GC.KeepAlive(tasks);
+ }
+
+ // Now that we're done and about to exit, if the wait completed and if we have
+ // any tasks with a notification bit set, signal the debugger if any requires it.
+ if (returnValue && notificationTasks != null)
+ {
+ // Loop through each task tha that had its bit set, and notify the debugger
+ // about the first one that requires it. The debugger will reset the bit
+ // for any tasks we don't notify of as soon as we break, so we only need to notify
+ // for one.
+ foreach (var task in notificationTasks)
+ {
+ if (task.NotifyDebuggerOfWaitCompletionIfNecessary()) break;
+ }
+ }
+
+ // If one or more threw exceptions, aggregate and throw them.
+ if (returnValue && (exceptionSeen || cancellationSeen))
+ {
+ // If the WaitAll was canceled and tasks were canceled but not faulted,
+ // prioritize throwing an OCE for canceling the WaitAll over throwing an
+ // AggregateException for all of the canceled Tasks. This helps
+ // to bring determinism to an otherwise non-determistic case of using
+ // the same token to cancel both the WaitAll and the Tasks.
+ if (!exceptionSeen) cancellationToken.ThrowIfCancellationRequested();
+
+ // Now gather up and throw all of the exceptions.
+ foreach (var task in tasks) AddExceptionsForCompletedTask(ref exceptions, task);
+ Contract.Assert(exceptions != null, "Should have seen at least one exception");
+ ThrowHelper.ThrowAggregateException(exceptions);
+ }
+
+ return returnValue;
+ }
+
+ /// <summary>Adds an element to the list, initializing the list if it's null.</summary>
+ /// <typeparam name="T">Specifies the type of data stored in the list.</typeparam>
+ /// <param name="item">The item to add.</param>
+ /// <param name="list">The list.</param>
+ /// <param name="initSize">The size to which to initialize the list if the list is null.</param>
+ private static void AddToList<T>(T item, ref List<T> list, int initSize)
+ {
+ if (list == null) list = new List<T>(initSize);
+ list.Add(item);
+ }
+
+ /// <summary>Performs a blocking WaitAll on the vetted list of tasks.</summary>
+ /// <param name="tasks">The tasks, which have already been checked and filtered for completion.</param>
+ /// <param name="millisecondsTimeout">The timeout.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>true if all of the tasks completed; otherwise, false.</returns>
+ private static bool WaitAllBlockingCore(List<Task> tasks, int millisecondsTimeout, CancellationToken cancellationToken)
+ {
+ Contract.Assert(tasks != null, "Expected a non-null list of tasks");
+ Contract.Assert(tasks.Count > 0, "Expected at least one task");
+
+ bool waitCompleted = false;
+ var mres = new SetOnCountdownMres(tasks.Count);
+ try
+ {
+ foreach (var task in tasks)
+ {
+ task.AddCompletionAction(mres, addBeforeOthers: true);
+ }
+ waitCompleted = mres.Wait(millisecondsTimeout, cancellationToken);
+ }
+ finally
+ {
+ if (!waitCompleted)
+ {
+ foreach (var task in tasks)
+ {
+ if (!task.IsCompleted) task.RemoveContinuation(mres);
+ }
+ }
+ // It's ok that we don't dispose of the MRES here, as we never
+ // access the MRES' WaitHandle, and thus no finalizable resources
+ // are actually created. We don't always just Dispose it because
+ // a continuation that's accessing the MRES could still be executing.
+ }
+ return waitCompleted;
+ }
+
+ // A ManualResetEventSlim that will get Set after Invoke is called count times.
+ // This allows us to replace this logic:
+ // var mres = new ManualResetEventSlim(tasks.Count);
+ // Action<Task> completionAction = delegate { if(Interlocked.Decrement(ref count) == 0) mres.Set(); };
+ // foreach(var task in tasks) task.AddCompletionAction(completionAction);
+ // with this logic:
+ // var mres = new SetOnCountdownMres(tasks.Count);
+ // foreach(var task in tasks) task.AddCompletionAction(mres);
+ // which saves a couple of allocations.
+ //
+ // Used in WaitAllBlockingCore (above).
+ private sealed class SetOnCountdownMres : ManualResetEventSlim, ITaskCompletionAction
+ {
+ private int _count;
+
+ internal SetOnCountdownMres(int count)
+ {
+ Contract.Assert(count > 0, "Expected count > 0");
+ _count = count;
+ }
+
+ public void Invoke(Task completingTask)
+ {
+ if (Interlocked.Decrement(ref _count) == 0) Set();
+ Contract.Assert(_count >= 0, "Count should never go below 0");
+ }
+
+ public bool InvokeMayRunArbitraryCode { get { return false; } }
+ }
+
+ /// <summary>
+ /// Internal WaitAll implementation which is meant to be used with small number of tasks,
+ /// optimized for Parallel.Invoke and other structured primitives.
+ /// </summary>
+ internal static void FastWaitAll(Task[] tasks)
+ {
+ Contract.Requires(tasks != null);
+
+ List<Exception> exceptions = null;
+
+ // Collects incomplete tasks in "waitedOnTaskList" and their cooperative events in "cooperativeEventList"
+ for (int i = tasks.Length - 1; i >= 0; i--)
+ {
+ if (!tasks[i].IsCompleted)
+ {
+ // Just attempting to inline here... result doesn't matter.
+ // We'll do a second pass to do actual wait on each task, and to aggregate their exceptions.
+ // If the task is inlined here, it will register as IsCompleted in the second pass
+ // and will just give us the exception.
+ tasks[i].WrappedTryRunInline();
+ }
+ }
+
+ // Wait on the tasks.
+ for (int i = tasks.Length - 1; i >= 0; i--)
+ {
+ var task = tasks[i];
+ task.SpinThenBlockingWait(Timeout.Infinite, default(CancellationToken));
+ AddExceptionsForCompletedTask(ref exceptions, task);
+
+ // Note that unlike other wait code paths, we do not check
+ // task.NotifyDebuggerOfWaitCompletionIfNecessary() here, because this method is currently
+ // only used from contexts where the tasks couldn't have that bit set, namely
+ // Parallel.Invoke. If that ever changes, such checks should be added here.
+ }
+
+ // If one or more threw exceptions, aggregate them.
+ if (exceptions != null)
+ {
+ ThrowHelper.ThrowAggregateException(exceptions);
+ }
+ }
+
+ /// <summary>
+ /// This internal function is only meant to be called by WaitAll()
+ /// If the completed task is canceled or it has other exceptions, here we will add those
+ /// into the passed in exception list (which will be lazily initialized here).
+ /// </summary>
+ internal static void AddExceptionsForCompletedTask(ref List<Exception> exceptions, Task t)
+ {
+ AggregateException ex = t.GetExceptions(true);
+ if (ex != null)
+ {
+ // make sure the task's exception observed status is set appropriately
+ // it's possible that WaitAll was called by the parent of an attached child,
+ // this will make sure it won't throw again in the implicit wait
+ t.UpdateExceptionObservedStatus();
+
+ if (exceptions == null)
+ {
+ exceptions = new List<Exception>(ex.InnerExceptions.Count);
+ }
+
+ exceptions.AddRange(ex.InnerExceptions);
+ }
+ }
+
+
+ /// <summary>
+ /// Waits for any of the provided <see cref="Task"/> objects to complete execution.
+ /// </summary>
+ /// <param name="tasks">
+ /// An array of <see cref="Task"/> instances on which to wait.
+ /// </param>
+ /// <returns>The index of the completed task in the <paramref name="tasks"/> array argument.</returns>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="tasks"/> argument is null.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentException">
+ /// The <paramref name="tasks"/> argument contains a null element.
+ /// </exception>
+ [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
+ public static int WaitAny(params Task[] tasks)
+ {
+ int waitResult = WaitAny(tasks, Timeout.Infinite);
+ Contract.Assert(tasks.Length == 0 || waitResult != -1, "expected wait to succeed");
+ return waitResult;
+ }
+
+ /// <summary>
+ /// Waits for any of the provided <see cref="Task"/> objects to complete execution.
+ /// </summary>
+ /// <param name="tasks">
+ /// An array of <see cref="Task"/> instances on which to wait.
+ /// </param>
+ /// <param name="timeout">
+ /// A <see cref="System.TimeSpan"/> that represents the number of milliseconds to wait, or a <see
+ /// cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely.
+ /// </param>
+ /// <returns>
+ /// The index of the completed task in the <paramref name="tasks"/> array argument, or -1 if the
+ /// timeout occurred.
+ /// </returns>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="tasks"/> argument is null.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentException">
+ /// The <paramref name="tasks"/> argument contains a null element.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentOutOfRangeException">
+ /// <paramref name="timeout"/> is a negative number other than -1 milliseconds, which represents an
+ /// infinite time-out -or- timeout is greater than
+ /// <see cref="System.Int32.MaxValue"/>.
+ /// </exception>
+ [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
+ public static int WaitAny(Task[] tasks, TimeSpan timeout)
+ {
+ long totalMilliseconds = (long)timeout.TotalMilliseconds;
+ if (totalMilliseconds < -1 || totalMilliseconds > Int32.MaxValue)
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.timeout);
+ }
+
+ return WaitAny(tasks, (int)totalMilliseconds);
+ }
+
+ /// <summary>
+ /// Waits for any of the provided <see cref="Task"/> objects to complete execution.
+ /// </summary>
+ /// <param name="tasks">
+ /// An array of <see cref="Task"/> instances on which to wait.
+ /// </param>
+ /// <param name="cancellationToken">
+ /// A <see cref="CancellationToken"/> to observe while waiting for a task to complete.
+ /// </param>
+ /// <returns>
+ /// The index of the completed task in the <paramref name="tasks"/> array argument.
+ /// </returns>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="tasks"/> argument is null.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentException">
+ /// The <paramref name="tasks"/> argument contains a null element.
+ /// </exception>
+ /// <exception cref="T:System.OperationCanceledException">
+ /// The <paramref name="cancellationToken"/> was canceled.
+ /// </exception>
+ [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
+ public static int WaitAny(Task[] tasks, CancellationToken cancellationToken)
+ {
+ return WaitAny(tasks, Timeout.Infinite, cancellationToken);
+ }
+
+ /// <summary>
+ /// Waits for any of the provided <see cref="Task"/> objects to complete execution.
+ /// </summary>
+ /// <param name="tasks">
+ /// An array of <see cref="Task"/> instances on which to wait.
+ /// </param>
+ /// <param name="millisecondsTimeout">
+ /// The number of milliseconds to wait, or <see cref="System.Threading.Timeout.Infinite"/> (-1) to
+ /// wait indefinitely.
+ /// </param>
+ /// <returns>
+ /// The index of the completed task in the <paramref name="tasks"/> array argument, or -1 if the
+ /// timeout occurred.
+ /// </returns>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="tasks"/> argument is null.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentException">
+ /// The <paramref name="tasks"/> argument contains a null element.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentOutOfRangeException">
+ /// <paramref name="millisecondsTimeout"/> is a negative number other than -1, which represents an
+ /// infinite time-out.
+ /// </exception>
+ [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
+ public static int WaitAny(Task[] tasks, int millisecondsTimeout)
+ {
+ return WaitAny(tasks, millisecondsTimeout, default(CancellationToken));
+ }
+
+ /// <summary>
+ /// Waits for any of the provided <see cref="Task"/> objects to complete execution.
+ /// </summary>
+ /// <param name="tasks">
+ /// An array of <see cref="Task"/> instances on which to wait.
+ /// </param>
+ /// <param name="millisecondsTimeout">
+ /// The number of milliseconds to wait, or <see cref="System.Threading.Timeout.Infinite"/> (-1) to
+ /// wait indefinitely.
+ /// </param>
+ /// <param name="cancellationToken">
+ /// A <see cref="CancellationToken"/> to observe while waiting for a task to complete.
+ /// </param>
+ /// <returns>
+ /// The index of the completed task in the <paramref name="tasks"/> array argument, or -1 if the
+ /// timeout occurred.
+ /// </returns>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="tasks"/> argument is null.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentException">
+ /// The <paramref name="tasks"/> argument contains a null element.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentOutOfRangeException">
+ /// <paramref name="millisecondsTimeout"/> is a negative number other than -1, which represents an
+ /// infinite time-out.
+ /// </exception>
+ /// <exception cref="T:System.OperationCanceledException">
+ /// The <paramref name="cancellationToken"/> was canceled.
+ /// </exception>
+ [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
+ public static int WaitAny(Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken)
+ {
+ if (tasks == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks);
+ }
+ if (millisecondsTimeout < -1)
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.millisecondsTimeout);
+ }
+ Contract.EndContractBlock();
+
+ cancellationToken.ThrowIfCancellationRequested(); // early check before we make any allocations
+
+ int signaledTaskIndex = -1;
+
+ // Make a pass through the loop to check for any tasks that may have
+ // already been completed, and to verify that no tasks are null.
+
+ for (int taskIndex = 0; taskIndex < tasks.Length; taskIndex++)
+ {
+ Task task = tasks[taskIndex];
+
+ if (task == null)
+ {
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Task_WaitMulti_NullTask, ExceptionArgument.tasks);
+ }
+
+ if (signaledTaskIndex == -1 && task.IsCompleted)
+ {
+ // We found our first completed task. Store it, but we can't just return here,
+ // as we still need to validate the whole array for nulls.
+ signaledTaskIndex = taskIndex;
+ }
+ }
+
+ if (signaledTaskIndex == -1 && tasks.Length != 0)
+ {
+ Task<Task> firstCompleted = TaskFactory.CommonCWAnyLogic(tasks);
+ bool waitCompleted = firstCompleted.Wait(millisecondsTimeout, cancellationToken);
+ if (waitCompleted)
+ {
+ Contract.Assert(firstCompleted.Status == TaskStatus.RanToCompletion);
+ signaledTaskIndex = Array.IndexOf(tasks, firstCompleted.Result);
+ Contract.Assert(signaledTaskIndex >= 0);
+ }
+ }
+
+ // We need to prevent the tasks array from being GC'ed until we come out of the wait.
+ // This is necessary so that the Parallel Debugger can traverse it during the long wait
+ // and deduce waiter/waitee relationships
+ GC.KeepAlive(tasks);
+
+ // Return the index
+ return signaledTaskIndex;
+ }
+
+ #region FromResult / FromException / FromCanceled
+
+ /// <summary>Creates a <see cref="Task{TResult}"/> that's completed successfully with the specified result.</summary>
+ /// <typeparam name="TResult">The type of the result returned by the task.</typeparam>
+ /// <param name="result">The result to store into the completed task.</param>
+ /// <returns>The successfully completed task.</returns>
+ public static Task<TResult> FromResult<TResult>(TResult result)
+ {
+ return new Task<TResult>(result);
+ }
+
+ /// <summary>Creates a <see cref="Task{TResult}"/> that's completed exceptionally with the specified exception.</summary>
+ /// <typeparam name="TResult">The type of the result returned by the task.</typeparam>
+ /// <param name="exception">The exception with which to complete the task.</param>
+ /// <returns>The faulted task.</returns>
+ public static Task FromException(Exception exception)
+ {
+ return FromException<VoidTaskResult>(exception);
+ }
+
+ /// <summary>Creates a <see cref="Task{TResult}"/> that's completed exceptionally with the specified exception.</summary>
+ /// <typeparam name="TResult">The type of the result returned by the task.</typeparam>
+ /// <param name="exception">The exception with which to complete the task.</param>
+ /// <returns>The faulted task.</returns>
+ public static Task<TResult> FromException<TResult>(Exception exception)
+ {
+ if (exception == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exception);
+ Contract.EndContractBlock();
+
+ var task = new Task<TResult>();
+ bool succeeded = task.TrySetException(exception);
+ Contract.Assert(succeeded, "This should always succeed on a new task.");
+ return task;
+ }
+
+ /// <summary>Creates a <see cref="Task"/> that's completed due to cancellation with the specified token.</summary>
+ /// <param name="cancellationToken">The token with which to complete the task.</param>
+ /// <returns>The canceled task.</returns>
+ public static Task FromCanceled(CancellationToken cancellationToken)
+ {
+ if (!cancellationToken.IsCancellationRequested)
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.cancellationToken);
+ Contract.EndContractBlock();
+ return new Task(true, TaskCreationOptions.None, cancellationToken);
+ }
+
+ /// <summary>Creates a <see cref="Task{TResult}"/> that's completed due to cancellation with the specified token.</summary>
+ /// <typeparam name="TResult">The type of the result returned by the task.</typeparam>
+ /// <param name="cancellationToken">The token with which to complete the task.</param>
+ /// <returns>The canceled task.</returns>
+ public static Task<TResult> FromCanceled<TResult>(CancellationToken cancellationToken)
+ {
+ if (!cancellationToken.IsCancellationRequested)
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.cancellationToken);
+ Contract.EndContractBlock();
+ return new Task<TResult>(true, default(TResult), TaskCreationOptions.None, cancellationToken);
+ }
+
+ /// <summary>Creates a <see cref="Task{TResult}"/> that's completed due to cancellation with the specified exception.</summary>
+ /// <typeparam name="TResult">The type of the result returned by the task.</typeparam>
+ /// <param name="exception">The exception with which to complete the task.</param>
+ /// <returns>The canceled task.</returns>
+ internal static Task<TResult> FromCancellation<TResult>(OperationCanceledException exception)
+ {
+ if (exception == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exception);
+ Contract.EndContractBlock();
+
+ var task = new Task<TResult>();
+ bool succeeded = task.TrySetCanceled(exception.CancellationToken, exception);
+ Contract.Assert(succeeded, "This should always succeed on a new task.");
+ return task;
+ }
+
+ /// <summary>Creates a <see cref="Task"/> that's completed due to cancellation with the specified token.</summary>
+ /// <param name="cancellationToken">The token with which to complete the task.</param>
+ /// <returns>The canceled task.</returns>
+ [FriendAccessAllowed]
+ internal static Task FromCancellation(CancellationToken cancellationToken)
+ {
+ return FromCanceled(cancellationToken);
+ }
+
+ /// <summary>Creates a <see cref="Task{TResult}"/> that's completed due to cancellation with the specified token.</summary>
+ /// <typeparam name="TResult">The type of the result returned by the task.</typeparam>
+ /// <param name="cancellationToken">The token with which to complete the task.</param>
+ /// <returns>The canceled task.</returns>
+ [FriendAccessAllowed]
+ internal static Task<TResult> FromCancellation<TResult>(CancellationToken cancellationToken)
+ {
+ return FromCanceled<TResult>(cancellationToken);
+ }
+
+ #endregion
+
+ #region Run methods
+
+
+ /// <summary>
+ /// Queues the specified work to run on the ThreadPool and returns a Task handle for that work.
+ /// </summary>
+ /// <param name="action">The work to execute asynchronously</param>
+ /// <returns>A Task that represents the work queued to execute in the ThreadPool.</returns>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="action"/> parameter was null.
+ /// </exception>
+ [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable
+ public static Task Run(Action action)
+ {
+ StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
+ return Task.InternalStartNew(null, action, null, default(CancellationToken), TaskScheduler.Default,
+ TaskCreationOptions.DenyChildAttach, InternalTaskOptions.None, ref stackMark);
+ }
+
+ /// <summary>
+ /// Queues the specified work to run on the ThreadPool and returns a Task handle for that work.
+ /// </summary>
+ /// <param name="action">The work to execute asynchronously</param>
+ /// <param name="cancellationToken">A cancellation token that should be used to cancel the work</param>
+ /// <returns>A Task that represents the work queued to execute in the ThreadPool.</returns>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="action"/> parameter was null.
+ /// </exception>
+ /// <exception cref="T:System.ObjectDisposedException">
+ /// The <see cref="T:System.CancellationTokenSource"/> associated with <paramref name="cancellationToken"/> was disposed.
+ /// </exception>
+ [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable
+ public static Task Run(Action action, CancellationToken cancellationToken)
+ {
+ StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
+ return Task.InternalStartNew(null, action, null, cancellationToken, TaskScheduler.Default,
+ TaskCreationOptions.DenyChildAttach, InternalTaskOptions.None, ref stackMark);
+ }
+
+ /// <summary>
+ /// Queues the specified work to run on the ThreadPool and returns a Task(TResult) handle for that work.
+ /// </summary>
+ /// <param name="function">The work to execute asynchronously</param>
+ /// <returns>A Task(TResult) that represents the work queued to execute in the ThreadPool.</returns>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="function"/> parameter was null.
+ /// </exception>
+ [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable
+ public static Task<TResult> Run<TResult>(Func<TResult> function)
+ {
+ StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
+ return Task<TResult>.StartNew(null, function, default(CancellationToken),
+ TaskCreationOptions.DenyChildAttach, InternalTaskOptions.None, TaskScheduler.Default, ref stackMark);
+ }
+
+ /// <summary>
+ /// Queues the specified work to run on the ThreadPool and returns a Task(TResult) handle for that work.
+ /// </summary>
+ /// <param name="function">The work to execute asynchronously</param>
+ /// <param name="cancellationToken">A cancellation token that should be used to cancel the work</param>
+ /// <returns>A Task(TResult) that represents the work queued to execute in the ThreadPool.</returns>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="function"/> parameter was null.
+ /// </exception>
+ /// <exception cref="T:System.ObjectDisposedException">
+ /// The <see cref="T:System.CancellationTokenSource"/> associated with <paramref name="cancellationToken"/> was disposed.
+ /// </exception>
+ [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable
+ public static Task<TResult> Run<TResult>(Func<TResult> function, CancellationToken cancellationToken)
+ {
+ StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
+ return Task<TResult>.StartNew(null, function, cancellationToken,
+ TaskCreationOptions.DenyChildAttach, InternalTaskOptions.None, TaskScheduler.Default, ref stackMark);
+ }
+
+ /// <summary>
+ /// Queues the specified work to run on the ThreadPool and returns a proxy for the
+ /// Task returned by <paramref name="function"/>.
+ /// </summary>
+ /// <param name="function">The work to execute asynchronously</param>
+ /// <returns>A Task that represents a proxy for the Task returned by <paramref name="function"/>.</returns>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="function"/> parameter was null.
+ /// </exception>
+ public static Task Run(Func<Task> function)
+ {
+ return Run(function, default(CancellationToken));
+ }
+
+
+ /// <summary>
+ /// Queues the specified work to run on the ThreadPool and returns a proxy for the
+ /// Task returned by <paramref name="function"/>.
+ /// </summary>
+ /// <param name="function">The work to execute asynchronously</param>
+ /// <param name="cancellationToken">A cancellation token that should be used to cancel the work</param>
+ /// <returns>A Task that represents a proxy for the Task returned by <paramref name="function"/>.</returns>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="function"/> parameter was null.
+ /// </exception>
+ /// <exception cref="T:System.ObjectDisposedException">
+ /// The <see cref="T:System.CancellationTokenSource"/> associated with <paramref name="cancellationToken"/> was disposed.
+ /// </exception>
+ public static Task Run(Func<Task> function, CancellationToken cancellationToken)
+ {
+ // Check arguments
+ if (function == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.function);
+ Contract.EndContractBlock();
+
+ if (AppContextSwitches.ThrowExceptionIfDisposedCancellationTokenSource)
+ {
+ cancellationToken.ThrowIfSourceDisposed();
+ }
+
+ // Short-circuit if we are given a pre-canceled token
+ if (cancellationToken.IsCancellationRequested)
+ return Task.FromCanceled(cancellationToken);
+
+ // Kick off initial Task, which will call the user-supplied function and yield a Task.
+ Task<Task> task1 = Task<Task>.Factory.StartNew(function, cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
+
+ // Create a promise-style Task to be used as a proxy for the operation
+ // Set lookForOce == true so that unwrap logic can be on the lookout for OCEs thrown as faults from task1, to support in-delegate cancellation.
+ UnwrapPromise<VoidTaskResult> promise = new UnwrapPromise<VoidTaskResult>(task1, lookForOce: true);
+
+ return promise;
+ }
+
+ /// <summary>
+ /// Queues the specified work to run on the ThreadPool and returns a proxy for the
+ /// Task(TResult) returned by <paramref name="function"/>.
+ /// </summary>
+ /// <typeparam name="TResult">The type of the result returned by the proxy Task.</typeparam>
+ /// <param name="function">The work to execute asynchronously</param>
+ /// <returns>A Task(TResult) that represents a proxy for the Task(TResult) returned by <paramref name="function"/>.</returns>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="function"/> parameter was null.
+ /// </exception>
+ public static Task<TResult> Run<TResult>(Func<Task<TResult>> function)
+ {
+ return Run(function, default(CancellationToken));
+ }
+
+ /// <summary>
+ /// Queues the specified work to run on the ThreadPool and returns a proxy for the
+ /// Task(TResult) returned by <paramref name="function"/>.
+ /// </summary>
+ /// <typeparam name="TResult">The type of the result returned by the proxy Task.</typeparam>
+ /// <param name="function">The work to execute asynchronously</param>
+ /// <param name="cancellationToken">A cancellation token that should be used to cancel the work</param>
+ /// <returns>A Task(TResult) that represents a proxy for the Task(TResult) returned by <paramref name="function"/>.</returns>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="function"/> parameter was null.
+ /// </exception>
+ public static Task<TResult> Run<TResult>(Func<Task<TResult>> function, CancellationToken cancellationToken)
+ {
+ // Check arguments
+ if (function == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.function);
+ Contract.EndContractBlock();
+
+ if (AppContextSwitches.ThrowExceptionIfDisposedCancellationTokenSource)
+ {
+ cancellationToken.ThrowIfSourceDisposed();
+ }
+
+ // Short-circuit if we are given a pre-canceled token
+ if (cancellationToken.IsCancellationRequested)
+ return Task.FromCanceled<TResult>(cancellationToken);
+
+ // Kick off initial Task, which will call the user-supplied function and yield a Task.
+ Task<Task<TResult>> task1 = Task<Task<TResult>>.Factory.StartNew(function, cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
+
+ // Create a promise-style Task to be used as a proxy for the operation
+ // Set lookForOce == true so that unwrap logic can be on the lookout for OCEs thrown as faults from task1, to support in-delegate cancellation.
+ UnwrapPromise<TResult> promise = new UnwrapPromise<TResult>(task1, lookForOce: true);
+
+ return promise;
+ }
+
+
+ #endregion
+
+ #region Delay methods
+
+ /// <summary>
+ /// Creates a Task that will complete after a time delay.
+ /// </summary>
+ /// <param name="delay">The time span to wait before completing the returned Task</param>
+ /// <returns>A Task that represents the time delay</returns>
+ /// <exception cref="T:System.ArgumentOutOfRangeException">
+ /// The <paramref name="delay"/> is less than -1 or greater than Int32.MaxValue.
+ /// </exception>
+ /// <remarks>
+ /// After the specified time delay, the Task is completed in RanToCompletion state.
+ /// </remarks>
+ public static Task Delay(TimeSpan delay)
+ {
+ return Delay(delay, default(CancellationToken));
+ }
+
+ /// <summary>
+ /// Creates a Task that will complete after a time delay.
+ /// </summary>
+ /// <param name="delay">The time span to wait before completing the returned Task</param>
+ /// <param name="cancellationToken">The cancellation token that will be checked prior to completing the returned Task</param>
+ /// <returns>A Task that represents the time delay</returns>
+ /// <exception cref="T:System.ArgumentOutOfRangeException">
+ /// The <paramref name="delay"/> is less than -1 or greater than Int32.MaxValue.
+ /// </exception>
+ /// <exception cref="T:System.ObjectDisposedException">
+ /// The provided <paramref name="cancellationToken"/> has already been disposed.
+ /// </exception>
+ /// <remarks>
+ /// If the cancellation token is signaled before the specified time delay, then the Task is completed in
+ /// Canceled state. Otherwise, the Task is completed in RanToCompletion state once the specified time
+ /// delay has expired.
+ /// </remarks>
+ public static Task Delay(TimeSpan delay, CancellationToken cancellationToken)
+ {
+ long totalMilliseconds = (long)delay.TotalMilliseconds;
+ if (totalMilliseconds < -1 || totalMilliseconds > Int32.MaxValue)
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.delay, ExceptionResource.Task_Delay_InvalidDelay);
+ }
+
+ return Delay((int)totalMilliseconds, cancellationToken);
+ }
+
+ /// <summary>
+ /// Creates a Task that will complete after a time delay.
+ /// </summary>
+ /// <param name="millisecondsDelay">The number of milliseconds to wait before completing the returned Task</param>
+ /// <returns>A Task that represents the time delay</returns>
+ /// <exception cref="T:System.ArgumentOutOfRangeException">
+ /// The <paramref name="millisecondsDelay"/> is less than -1.
+ /// </exception>
+ /// <remarks>
+ /// After the specified time delay, the Task is completed in RanToCompletion state.
+ /// </remarks>
+ public static Task Delay(int millisecondsDelay)
+ {
+ return Delay(millisecondsDelay, default(CancellationToken));
+ }
+
+ /// <summary>
+ /// Creates a Task that will complete after a time delay.
+ /// </summary>
+ /// <param name="millisecondsDelay">The number of milliseconds to wait before completing the returned Task</param>
+ /// <param name="cancellationToken">The cancellation token that will be checked prior to completing the returned Task</param>
+ /// <returns>A Task that represents the time delay</returns>
+ /// <exception cref="T:System.ArgumentOutOfRangeException">
+ /// The <paramref name="millisecondsDelay"/> is less than -1.
+ /// </exception>
+ /// <exception cref="T:System.ObjectDisposedException">
+ /// The provided <paramref name="cancellationToken"/> has already been disposed.
+ /// </exception>
+ /// <remarks>
+ /// If the cancellation token is signaled before the specified time delay, then the Task is completed in
+ /// Canceled state. Otherwise, the Task is completed in RanToCompletion state once the specified time
+ /// delay has expired.
+ /// </remarks>
+ public static Task Delay(int millisecondsDelay, CancellationToken cancellationToken)
+ {
+ // Throw on non-sensical time
+ if (millisecondsDelay < -1)
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.millisecondsDelay, ExceptionResource.Task_Delay_InvalidMillisecondsDelay);
+ }
+ Contract.EndContractBlock();
+
+ // some short-cuts in case quick completion is in order
+ if (cancellationToken.IsCancellationRequested)
+ {
+ // return a Task created as already-Canceled
+ return Task.FromCanceled(cancellationToken);
+ }
+ else if (millisecondsDelay == 0)
+ {
+ // return a Task created as already-RanToCompletion
+ return Task.CompletedTask;
+ }
+
+ // Construct a promise-style Task to encapsulate our return value
+ var promise = new DelayPromise(cancellationToken);
+
+ // Register our cancellation token, if necessary.
+ if (cancellationToken.CanBeCanceled)
+ {
+ promise.Registration = cancellationToken.InternalRegisterWithoutEC(state => ((DelayPromise)state).Complete(), promise);
+ }
+
+ // ... and create our timer and make sure that it stays rooted.
+ if (millisecondsDelay != Timeout.Infinite) // no need to create the timer if it's an infinite timeout
+ {
+ promise.Timer = new Timer(state => ((DelayPromise)state).Complete(), promise, millisecondsDelay, Timeout.Infinite);
+ promise.Timer.KeepRootedWhileScheduled();
+ }
+
+ // Return the timer proxy task
+ return promise;
+ }
+
+ /// <summary>Task that also stores the completion closure and logic for Task.Delay implementation.</summary>
+ private sealed class DelayPromise : Task<VoidTaskResult>
+ {
+ internal DelayPromise(CancellationToken token)
+ : base()
+ {
+ this.Token = token;
+ if (AsyncCausalityTracer.LoggingOn)
+ AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, this.Id, "Task.Delay", 0);
+
+ if (Task.s_asyncDebuggingEnabled)
+ {
+ AddToActiveTasks(this);
+ }
+ }
+
+ internal readonly CancellationToken Token;
+ internal CancellationTokenRegistration Registration;
+ internal Timer Timer;
+
+ internal void Complete()
+ {
+ // Transition the task to completed.
+ bool setSucceeded;
+
+ if (Token.IsCancellationRequested)
+ {
+ setSucceeded = TrySetCanceled(Token);
+ }
+ else
+ {
+ if (AsyncCausalityTracer.LoggingOn)
+ AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, this.Id, AsyncCausalityStatus.Completed);
+
+ if (Task.s_asyncDebuggingEnabled)
+ {
+ RemoveFromActiveTasks(this.Id);
+ }
+ setSucceeded = TrySetResult(default(VoidTaskResult));
+ }
+
+ // If we set the value, also clean up.
+ if (setSucceeded)
+ {
+ if (Timer != null) Timer.Dispose();
+ Registration.Dispose();
+ }
+ }
+ }
+ #endregion
+
+ #region WhenAll
+ /// <summary>
+ /// Creates a task that will complete when all of the supplied tasks have completed.
+ /// </summary>
+ /// <param name="tasks">The tasks to wait on for completion.</param>
+ /// <returns>A task that represents the completion of all of the supplied tasks.</returns>
+ /// <remarks>
+ /// <para>
+ /// If any of the supplied tasks completes in a faulted state, the returned task will also complete in a Faulted state,
+ /// where its exceptions will contain the aggregation of the set of unwrapped exceptions from each of the supplied tasks.
+ /// </para>
+ /// <para>
+ /// If none of the supplied tasks faulted but at least one of them was canceled, the returned task will end in the Canceled state.
+ /// </para>
+ /// <para>
+ /// If none of the tasks faulted and none of the tasks were canceled, the resulting task will end in the RanToCompletion state.
+ /// </para>
+ /// <para>
+ /// If the supplied array/enumerable contains no tasks, the returned task will immediately transition to a RanToCompletion
+ /// state before it's returned to the caller.
+ /// </para>
+ /// </remarks>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="tasks"/> argument was null.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentException">
+ /// The <paramref name="tasks"/> collection contained a null task.
+ /// </exception>
+ public static Task WhenAll(IEnumerable<Task> tasks)
+ {
+ // Take a more efficient path if tasks is actually an array
+ Task[] taskArray = tasks as Task[];
+ if (taskArray != null)
+ {
+ return WhenAll(taskArray);
+ }
+
+ // Skip a List allocation/copy if tasks is a collection
+ ICollection<Task> taskCollection = tasks as ICollection<Task>;
+ if (taskCollection != null)
+ {
+ int index = 0;
+ taskArray = new Task[taskCollection.Count];
+ foreach (var task in tasks)
+ {
+ if (task == null) ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_NullTask, ExceptionArgument.tasks);
+ taskArray[index++] = task;
+ }
+ return InternalWhenAll(taskArray);
+ }
+
+ // Do some argument checking and convert tasks to a List (and later an array).
+ if (tasks == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks);
+ List<Task> taskList = new List<Task>();
+ foreach (Task task in tasks)
+ {
+ if (task == null) ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_NullTask, ExceptionArgument.tasks);
+ taskList.Add(task);
+ }
+
+ // Delegate the rest to InternalWhenAll()
+ return InternalWhenAll(taskList.ToArray());
+ }
+
+ /// <summary>
+ /// Creates a task that will complete when all of the supplied tasks have completed.
+ /// </summary>
+ /// <param name="tasks">The tasks to wait on for completion.</param>
+ /// <returns>A task that represents the completion of all of the supplied tasks.</returns>
+ /// <remarks>
+ /// <para>
+ /// If any of the supplied tasks completes in a faulted state, the returned task will also complete in a Faulted state,
+ /// where its exceptions will contain the aggregation of the set of unwrapped exceptions from each of the supplied tasks.
+ /// </para>
+ /// <para>
+ /// If none of the supplied tasks faulted but at least one of them was canceled, the returned task will end in the Canceled state.
+ /// </para>
+ /// <para>
+ /// If none of the tasks faulted and none of the tasks were canceled, the resulting task will end in the RanToCompletion state.
+ /// </para>
+ /// <para>
+ /// If the supplied array/enumerable contains no tasks, the returned task will immediately transition to a RanToCompletion
+ /// state before it's returned to the caller.
+ /// </para>
+ /// </remarks>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="tasks"/> argument was null.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentException">
+ /// The <paramref name="tasks"/> array contained a null task.
+ /// </exception>
+ public static Task WhenAll(params Task[] tasks)
+ {
+ // Do some argument checking and make a defensive copy of the tasks array
+ if (tasks == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks);
+ Contract.EndContractBlock();
+
+ int taskCount = tasks.Length;
+ if (taskCount == 0) return InternalWhenAll(tasks); // Small optimization in the case of an empty array.
+
+ Task[] tasksCopy = new Task[taskCount];
+ for (int i = 0; i < taskCount; i++)
+ {
+ Task task = tasks[i];
+ if (task == null) ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_NullTask, ExceptionArgument.tasks);
+ tasksCopy[i] = task;
+ }
+
+ // The rest can be delegated to InternalWhenAll()
+ return InternalWhenAll(tasksCopy);
+ }
+
+ // Some common logic to support WhenAll() methods
+ // tasks should be a defensive copy.
+ private static Task InternalWhenAll(Task[] tasks)
+ {
+ Contract.Requires(tasks != null, "Expected a non-null tasks array");
+ return (tasks.Length == 0) ? // take shortcut if there are no tasks upon which to wait
+ Task.CompletedTask :
+ new WhenAllPromise(tasks);
+ }
+
+ // A Task<VoidTaskResult> that gets completed when all of its constituent tasks complete.
+ // Completion logic will analyze the antecedents in order to choose completion status.
+ // This type allows us to replace this logic:
+ // Task<VoidTaskResult> promise = new Task<VoidTaskResult>(...);
+ // Action<Task> completionAction = delegate { <completion logic>};
+ // TaskFactory.CommonCWAllLogic(tasksCopy).AddCompletionAction(completionAction);
+ // return promise;
+ // which involves several allocations, with this logic:
+ // return new WhenAllPromise(tasksCopy);
+ // which saves a couple of allocations and enables debugger notification specialization.
+ //
+ // Used in InternalWhenAll(Task[])
+ private sealed class WhenAllPromise : Task<VoidTaskResult>, ITaskCompletionAction
+ {
+ /// <summary>
+ /// Stores all of the constituent tasks. Tasks clear themselves out of this
+ /// array as they complete, but only if they don't have their wait notification bit set.
+ /// </summary>
+ private readonly Task[] m_tasks;
+ /// <summary>The number of tasks remaining to complete.</summary>
+ private int m_count;
+
+ internal WhenAllPromise(Task[] tasks) :
+ base()
+ {
+ Contract.Requires(tasks != null, "Expected a non-null task array");
+ Contract.Requires(tasks.Length > 0, "Expected a non-zero length task array");
+
+ if (AsyncCausalityTracer.LoggingOn)
+ AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, this.Id, "Task.WhenAll", 0);
+
+ if (s_asyncDebuggingEnabled)
+ {
+ AddToActiveTasks(this);
+ }
+
+ m_tasks = tasks;
+ m_count = tasks.Length;
+
+ foreach (var task in tasks)
+ {
+ if (task.IsCompleted) this.Invoke(task); // short-circuit the completion action, if possible
+ else task.AddCompletionAction(this); // simple completion action
+ }
+ }
+
+ public void Invoke(Task completedTask)
+ {
+ if (AsyncCausalityTracer.LoggingOn)
+ AsyncCausalityTracer.TraceOperationRelation(CausalityTraceLevel.Important, this.Id, CausalityRelation.Join);
+
+ // Decrement the count, and only continue to complete the promise if we're the last one.
+ if (Interlocked.Decrement(ref m_count) == 0)
+ {
+ // Set up some accounting variables
+ List<ExceptionDispatchInfo> observedExceptions = null;
+ Task canceledTask = null;
+
+ // Loop through antecedents:
+ // If any one of them faults, the result will be faulted
+ // If none fault, but at least one is canceled, the result will be canceled
+ // If none fault or are canceled, then result will be RanToCompletion
+ for (int i = 0; i < m_tasks.Length; i++)
+ {
+ var task = m_tasks[i];
+ Contract.Assert(task != null, "Constituent task in WhenAll should never be null");
+
+ if (task.IsFaulted)
+ {
+ if (observedExceptions == null) observedExceptions = new List<ExceptionDispatchInfo>();
+ observedExceptions.AddRange(task.GetExceptionDispatchInfos());
+ }
+ else if (task.IsCanceled)
+ {
+ if (canceledTask == null) canceledTask = task; // use the first task that's canceled
+ }
+
+ // Regardless of completion state, if the task has its debug bit set, transfer it to the
+ // WhenAll task. We must do this before we complete the task.
+ if (task.IsWaitNotificationEnabled) this.SetNotificationForWaitCompletion(enabled: true);
+ else m_tasks[i] = null; // avoid holding onto tasks unnecessarily
+ }
+
+ if (observedExceptions != null)
+ {
+ Contract.Assert(observedExceptions.Count > 0, "Expected at least one exception");
+
+ //We don't need to TraceOperationCompleted here because TrySetException will call Finish and we'll log it there
+
+ TrySetException(observedExceptions);
+ }
+ else if (canceledTask != null)
+ {
+ TrySetCanceled(canceledTask.CancellationToken, canceledTask.GetCancellationExceptionDispatchInfo());
+ }
+ else
+ {
+ if (AsyncCausalityTracer.LoggingOn)
+ AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, this.Id, AsyncCausalityStatus.Completed);
+
+ if (Task.s_asyncDebuggingEnabled)
+ {
+ RemoveFromActiveTasks(this.Id);
+ }
+ TrySetResult(default(VoidTaskResult));
+ }
+ }
+ Contract.Assert(m_count >= 0, "Count should never go below 0");
+ }
+
+ public bool InvokeMayRunArbitraryCode { get { return true; } }
+
+ /// <summary>
+ /// Returns whether we should notify the debugger of a wait completion. This returns
+ /// true iff at least one constituent task has its bit set.
+ /// </summary>
+ internal override bool ShouldNotifyDebuggerOfWaitCompletion
+ {
+ get
+ {
+ return
+ base.ShouldNotifyDebuggerOfWaitCompletion &&
+ Task.AnyTaskRequiresNotifyDebuggerOfWaitCompletion(m_tasks);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Creates a task that will complete when all of the supplied tasks have completed.
+ /// </summary>
+ /// <param name="tasks">The tasks to wait on for completion.</param>
+ /// <returns>A task that represents the completion of all of the supplied tasks.</returns>
+ /// <remarks>
+ /// <para>
+ /// If any of the supplied tasks completes in a faulted state, the returned task will also complete in a Faulted state,
+ /// where its exceptions will contain the aggregation of the set of unwrapped exceptions from each of the supplied tasks.
+ /// </para>
+ /// <para>
+ /// If none of the supplied tasks faulted but at least one of them was canceled, the returned task will end in the Canceled state.
+ /// </para>
+ /// <para>
+ /// If none of the tasks faulted and none of the tasks were canceled, the resulting task will end in the RanToCompletion state.
+ /// The Result of the returned task will be set to an array containing all of the results of the
+ /// supplied tasks in the same order as they were provided (e.g. if the input tasks array contained t1, t2, t3, the output
+ /// task's Result will return an TResult[] where arr[0] == t1.Result, arr[1] == t2.Result, and arr[2] == t3.Result).
+ /// </para>
+ /// <para>
+ /// If the supplied array/enumerable contains no tasks, the returned task will immediately transition to a RanToCompletion
+ /// state before it's returned to the caller. The returned TResult[] will be an array of 0 elements.
+ /// </para>
+ /// </remarks>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="tasks"/> argument was null.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentException">
+ /// The <paramref name="tasks"/> collection contained a null task.
+ /// </exception>
+ public static Task<TResult[]> WhenAll<TResult>(IEnumerable<Task<TResult>> tasks)
+ {
+ // Take a more efficient route if tasks is actually an array
+ Task<TResult>[] taskArray = tasks as Task<TResult>[];
+ if (taskArray != null)
+ {
+ return WhenAll<TResult>(taskArray);
+ }
+
+ // Skip a List allocation/copy if tasks is a collection
+ ICollection<Task<TResult>> taskCollection = tasks as ICollection<Task<TResult>>;
+ if (taskCollection != null)
+ {
+ int index = 0;
+ taskArray = new Task<TResult>[taskCollection.Count];
+ foreach (var task in tasks)
+ {
+ if (task == null) ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_NullTask, ExceptionArgument.tasks);
+ taskArray[index++] = task;
+ }
+ return InternalWhenAll<TResult>(taskArray);
+ }
+
+ // Do some argument checking and convert tasks into a List (later an array)
+ if (tasks == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks);
+ List<Task<TResult>> taskList = new List<Task<TResult>>();
+ foreach (Task<TResult> task in tasks)
+ {
+ if (task == null) ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_NullTask, ExceptionArgument.tasks);
+ taskList.Add(task);
+ }
+
+ // Delegate the rest to InternalWhenAll<TResult>().
+ return InternalWhenAll<TResult>(taskList.ToArray());
+ }
+
+ /// <summary>
+ /// Creates a task that will complete when all of the supplied tasks have completed.
+ /// </summary>
+ /// <param name="tasks">The tasks to wait on for completion.</param>
+ /// <returns>A task that represents the completion of all of the supplied tasks.</returns>
+ /// <remarks>
+ /// <para>
+ /// If any of the supplied tasks completes in a faulted state, the returned task will also complete in a Faulted state,
+ /// where its exceptions will contain the aggregation of the set of unwrapped exceptions from each of the supplied tasks.
+ /// </para>
+ /// <para>
+ /// If none of the supplied tasks faulted but at least one of them was canceled, the returned task will end in the Canceled state.
+ /// </para>
+ /// <para>
+ /// If none of the tasks faulted and none of the tasks were canceled, the resulting task will end in the RanToCompletion state.
+ /// The Result of the returned task will be set to an array containing all of the results of the
+ /// supplied tasks in the same order as they were provided (e.g. if the input tasks array contained t1, t2, t3, the output
+ /// task's Result will return an TResult[] where arr[0] == t1.Result, arr[1] == t2.Result, and arr[2] == t3.Result).
+ /// </para>
+ /// <para>
+ /// If the supplied array/enumerable contains no tasks, the returned task will immediately transition to a RanToCompletion
+ /// state before it's returned to the caller. The returned TResult[] will be an array of 0 elements.
+ /// </para>
+ /// </remarks>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="tasks"/> argument was null.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentException">
+ /// The <paramref name="tasks"/> array contained a null task.
+ /// </exception>
+ public static Task<TResult[]> WhenAll<TResult>(params Task<TResult>[] tasks)
+ {
+ // Do some argument checking and make a defensive copy of the tasks array
+ if (tasks == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks);
+ Contract.EndContractBlock();
+
+ int taskCount = tasks.Length;
+ if (taskCount == 0) return InternalWhenAll<TResult>(tasks); // small optimization in the case of an empty task array
+
+ Task<TResult>[] tasksCopy = new Task<TResult>[taskCount];
+ for (int i = 0; i < taskCount; i++)
+ {
+ Task<TResult> task = tasks[i];
+ if (task == null) ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_NullTask, ExceptionArgument.tasks);
+ tasksCopy[i] = task;
+ }
+
+ // Delegate the rest to InternalWhenAll<TResult>()
+ return InternalWhenAll<TResult>(tasksCopy);
+ }
+
+ // Some common logic to support WhenAll<TResult> methods
+ private static Task<TResult[]> InternalWhenAll<TResult>(Task<TResult>[] tasks)
+ {
+ Contract.Requires(tasks != null, "Expected a non-null tasks array");
+ return (tasks.Length == 0) ? // take shortcut if there are no tasks upon which to wait
+ new Task<TResult[]>(false, new TResult[0], TaskCreationOptions.None, default(CancellationToken)) :
+ new WhenAllPromise<TResult>(tasks);
+ }
+
+ // A Task<T> that gets completed when all of its constituent tasks complete.
+ // Completion logic will analyze the antecedents in order to choose completion status.
+ // See comments for non-generic version of WhenAllPromise class.
+ //
+ // Used in InternalWhenAll<TResult>(Task<TResult>[])
+ private sealed class WhenAllPromise<T> : Task<T[]>, ITaskCompletionAction
+ {
+ /// <summary>
+ /// Stores all of the constituent tasks. Tasks clear themselves out of this
+ /// array as they complete, but only if they don't have their wait notification bit set.
+ /// </summary>
+ private readonly Task<T>[] m_tasks;
+ /// <summary>The number of tasks remaining to complete.</summary>
+ private int m_count;
+
+ internal WhenAllPromise(Task<T>[] tasks) :
+ base()
+ {
+ Contract.Requires(tasks != null, "Expected a non-null task array");
+ Contract.Requires(tasks.Length > 0, "Expected a non-zero length task array");
+
+ m_tasks = tasks;
+ m_count = tasks.Length;
+
+ if (AsyncCausalityTracer.LoggingOn)
+ AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, this.Id, "Task.WhenAll", 0);
+
+ if (s_asyncDebuggingEnabled)
+ {
+ AddToActiveTasks(this);
+ }
+
+ foreach (var task in tasks)
+ {
+ if (task.IsCompleted) this.Invoke(task); // short-circuit the completion action, if possible
+ else task.AddCompletionAction(this); // simple completion action
+ }
+ }
+
+ public void Invoke(Task ignored)
+ {
+ if (AsyncCausalityTracer.LoggingOn)
+ AsyncCausalityTracer.TraceOperationRelation(CausalityTraceLevel.Important, this.Id, CausalityRelation.Join);
+
+ // Decrement the count, and only continue to complete the promise if we're the last one.
+ if (Interlocked.Decrement(ref m_count) == 0)
+ {
+ // Set up some accounting variables
+ T[] results = new T[m_tasks.Length];
+ List<ExceptionDispatchInfo> observedExceptions = null;
+ Task canceledTask = null;
+
+ // Loop through antecedents:
+ // If any one of them faults, the result will be faulted
+ // If none fault, but at least one is canceled, the result will be canceled
+ // If none fault or are canceled, then result will be RanToCompletion
+ for (int i = 0; i < m_tasks.Length; i++)
+ {
+ Task<T> task = m_tasks[i];
+ Contract.Assert(task != null, "Constituent task in WhenAll should never be null");
+
+ if (task.IsFaulted)
+ {
+ if (observedExceptions == null) observedExceptions = new List<ExceptionDispatchInfo>();
+ observedExceptions.AddRange(task.GetExceptionDispatchInfos());
+ }
+ else if (task.IsCanceled)
+ {
+ if (canceledTask == null) canceledTask = task; // use the first task that's canceled
+ }
+ else
+ {
+ Contract.Assert(task.Status == TaskStatus.RanToCompletion);
+ results[i] = task.GetResultCore(waitCompletionNotification: false); // avoid Result, which would triggering debug notification
+ }
+
+ // Regardless of completion state, if the task has its debug bit set, transfer it to the
+ // WhenAll task. We must do this before we complete the task.
+ if (task.IsWaitNotificationEnabled) this.SetNotificationForWaitCompletion(enabled: true);
+ else m_tasks[i] = null; // avoid holding onto tasks unnecessarily
+ }
+
+ if (observedExceptions != null)
+ {
+ Contract.Assert(observedExceptions.Count > 0, "Expected at least one exception");
+
+ //We don't need to TraceOperationCompleted here because TrySetException will call Finish and we'll log it there
+
+ TrySetException(observedExceptions);
+ }
+ else if (canceledTask != null)
+ {
+ TrySetCanceled(canceledTask.CancellationToken, canceledTask.GetCancellationExceptionDispatchInfo());
+ }
+ else
+ {
+ if (AsyncCausalityTracer.LoggingOn)
+ AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, this.Id, AsyncCausalityStatus.Completed);
+
+ if (Task.s_asyncDebuggingEnabled)
+ {
+ RemoveFromActiveTasks(this.Id);
+ }
+ TrySetResult(results);
+ }
+ }
+ Contract.Assert(m_count >= 0, "Count should never go below 0");
+ }
+
+ public bool InvokeMayRunArbitraryCode { get { return true; } }
+
+ /// <summary>
+ /// Returns whether we should notify the debugger of a wait completion. This returns true
+ /// iff at least one constituent task has its bit set.
+ /// </summary>
+ internal override bool ShouldNotifyDebuggerOfWaitCompletion
+ {
+ get
+ {
+ return
+ base.ShouldNotifyDebuggerOfWaitCompletion &&
+ Task.AnyTaskRequiresNotifyDebuggerOfWaitCompletion(m_tasks);
+ }
+ }
+ }
+ #endregion
+
+ #region WhenAny
+ /// <summary>
+ /// Creates a task that will complete when any of the supplied tasks have completed.
+ /// </summary>
+ /// <param name="tasks">The tasks to wait on for completion.</param>
+ /// <returns>A task that represents the completion of one of the supplied tasks. The return Task's Result is the task that completed.</returns>
+ /// <remarks>
+ /// The returned task will complete when any of the supplied tasks has completed. The returned task will always end in the RanToCompletion state
+ /// 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.
+ /// </remarks>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="tasks"/> argument was null.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentException">
+ /// The <paramref name="tasks"/> array contained a null task, or was empty.
+ /// </exception>
+ public static Task<Task> WhenAny(params Task[] tasks)
+ {
+ if (tasks == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks);
+ if (tasks.Length == 0)
+ {
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_EmptyTaskList, ExceptionArgument.tasks);
+ }
+ Contract.EndContractBlock();
+
+ // Make a defensive copy, as the user may manipulate the tasks array
+ // after we return but before the WhenAny asynchronously completes.
+ int taskCount = tasks.Length;
+ Task[] tasksCopy = new Task[taskCount];
+ for (int i = 0; i < taskCount; i++)
+ {
+ Task task = tasks[i];
+ if (task == null) ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_NullTask, ExceptionArgument.tasks);
+ tasksCopy[i] = task;
+ }
+
+ // Previously implemented CommonCWAnyLogic() can handle the rest
+ return TaskFactory.CommonCWAnyLogic(tasksCopy);
+ }
+
+ /// <summary>
+ /// Creates a task that will complete when any of the supplied tasks have completed.
+ /// </summary>
+ /// <param name="tasks">The tasks to wait on for completion.</param>
+ /// <returns>A task that represents the completion of one of the supplied tasks. The return Task's Result is the task that completed.</returns>
+ /// <remarks>
+ /// The returned task will complete when any of the supplied tasks has completed. The returned task will always end in the RanToCompletion state
+ /// 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.
+ /// </remarks>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="tasks"/> argument was null.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentException">
+ /// The <paramref name="tasks"/> collection contained a null task, or was empty.
+ /// </exception>
+ public static Task<Task> WhenAny(IEnumerable<Task> tasks)
+ {
+ if (tasks == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks);
+ Contract.EndContractBlock();
+
+ // Make a defensive copy, as the user may manipulate the tasks collection
+ // after we return but before the WhenAny asynchronously completes.
+ List<Task> taskList = new List<Task>();
+ foreach (Task task in tasks)
+ {
+ if (task == null) ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_NullTask, ExceptionArgument.tasks);
+ taskList.Add(task);
+ }
+
+ if (taskList.Count == 0)
+ {
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_EmptyTaskList, ExceptionArgument.tasks);
+ }
+
+ // Previously implemented CommonCWAnyLogic() can handle the rest
+ return TaskFactory.CommonCWAnyLogic(taskList);
+ }
+
+ /// <summary>
+ /// Creates a task that will complete when any of the supplied tasks have completed.
+ /// </summary>
+ /// <param name="tasks">The tasks to wait on for completion.</param>
+ /// <returns>A task that represents the completion of one of the supplied tasks. The return Task's Result is the task that completed.</returns>
+ /// <remarks>
+ /// The returned task will complete when any of the supplied tasks has completed. The returned task will always end in the RanToCompletion state
+ /// 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.
+ /// </remarks>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="tasks"/> argument was null.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentException">
+ /// The <paramref name="tasks"/> array contained a null task, or was empty.
+ /// </exception>
+ public static Task<Task<TResult>> WhenAny<TResult>(params Task<TResult>[] tasks)
+ {
+ // We would just like to do this:
+ // return (Task<Task<TResult>>) WhenAny( (Task[]) tasks);
+ // but classes are not covariant to enable casting Task<TResult> to Task<Task<TResult>>.
+
+ // Call WhenAny(Task[]) for basic functionality
+ Task<Task> intermediate = WhenAny((Task[])tasks);
+
+ // Return a continuation task with the correct result type
+ return intermediate.ContinueWith(Task<TResult>.TaskWhenAnyCast, default(CancellationToken),
+ TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default);
+ }
+
+ /// <summary>
+ /// Creates a task that will complete when any of the supplied tasks have completed.
+ /// </summary>
+ /// <param name="tasks">The tasks to wait on for completion.</param>
+ /// <returns>A task that represents the completion of one of the supplied tasks. The return Task's Result is the task that completed.</returns>
+ /// <remarks>
+ /// The returned task will complete when any of the supplied tasks has completed. The returned task will always end in the RanToCompletion state
+ /// 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.
+ /// </remarks>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="tasks"/> argument was null.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentException">
+ /// The <paramref name="tasks"/> collection contained a null task, or was empty.
+ /// </exception>
+ public static Task<Task<TResult>> WhenAny<TResult>(IEnumerable<Task<TResult>> tasks)
+ {
+ // We would just like to do this:
+ // return (Task<Task<TResult>>) WhenAny( (IEnumerable<Task>) tasks);
+ // but classes are not covariant to enable casting Task<TResult> to Task<Task<TResult>>.
+
+ // Call WhenAny(IEnumerable<Task>) for basic functionality
+ Task<Task> intermediate = WhenAny((IEnumerable<Task>)tasks);
+
+ // Return a continuation task with the correct result type
+ return intermediate.ContinueWith(Task<TResult>.TaskWhenAnyCast, default(CancellationToken),
+ TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default);
+ }
+ #endregion
+
+ [FriendAccessAllowed]
+ internal static Task<TResult> CreateUnwrapPromise<TResult>(Task outerTask, bool lookForOce)
+ {
+ Contract.Requires(outerTask != null);
+
+ return new UnwrapPromise<TResult>(outerTask, lookForOce);
+ }
+
+ internal virtual Delegate[] GetDelegateContinuationsForDebugger()
+ {
+ //Avoid an infinite loop by making sure the continuation object is not a reference to istelf.
+ if (this.m_continuationObject != this)
+ return GetDelegatesFromContinuationObject(this.m_continuationObject);
+ else
+ return null;
+ }
+
+ internal static Delegate[] GetDelegatesFromContinuationObject(object continuationObject)
+ {
+ if (continuationObject != null)
+ {
+ Action singleAction = continuationObject as Action;
+ if (singleAction != null)
+ {
+ return new Delegate[] { AsyncMethodBuilderCore.TryGetStateMachineForDebugger(singleAction) };
+ }
+
+ TaskContinuation taskContinuation = continuationObject as TaskContinuation;
+ if (taskContinuation != null)
+ {
+ return taskContinuation.GetDelegateContinuationsForDebugger();
+ }
+
+ Task continuationTask = continuationObject as Task;
+ if (continuationTask != null)
+ {
+ Contract.Assert(continuationTask.m_action == null);
+ Delegate[] delegates = continuationTask.GetDelegateContinuationsForDebugger();
+ if (delegates != null)
+ return delegates;
+ }
+
+ //We need this ITaskCompletionAction after the Task because in the case of UnwrapPromise
+ //the VS debugger is more interested in the continuation than the internal invoke()
+ ITaskCompletionAction singleCompletionAction = continuationObject as ITaskCompletionAction;
+ if (singleCompletionAction != null)
+ {
+ return new Delegate[] { new Action<Task>(singleCompletionAction.Invoke) };
+ }
+
+ List<object> continuationList = continuationObject as List<object>;
+ if (continuationList != null)
+ {
+ List<Delegate> result = new List<Delegate>();
+ foreach (object obj in continuationList)
+ {
+ var innerDelegates = GetDelegatesFromContinuationObject(obj);
+ if (innerDelegates != null)
+ {
+ foreach (var del in innerDelegates)
+ {
+ if (del != null)
+ result.Add(del);
+ }
+ }
+ }
+
+ return result.ToArray();
+ }
+ }
+
+ return null;
+ }
+
+ private static Task GetActiveTaskFromId(int taskId)
+ {
+ Task task = null;
+ s_currentActiveTasks.TryGetValue(taskId, out task);
+ return task;
+ }
+
+ private static Task[] GetActiveTasks()
+ {
+
+ return new List<Task>(s_currentActiveTasks.Values).ToArray();
+ }
+
+
+ }
+
+ internal sealed class CompletionActionInvoker : IThreadPoolWorkItem
+ {
+ private readonly ITaskCompletionAction m_action;
+ private readonly Task m_completingTask;
+
+ internal CompletionActionInvoker(ITaskCompletionAction action, Task completingTask)
+ {
+ m_action = action;
+ m_completingTask = completingTask;
+ }
+
+ [SecurityCritical]
+ void IThreadPoolWorkItem.ExecuteWorkItem()
+ {
+ m_action.Invoke(m_completingTask);
+ }
+
+ [SecurityCritical]
+ void IThreadPoolWorkItem.MarkAborted(ThreadAbortException tae)
+ {
+ /* NOP */
+ }
+ }
+
+ // Proxy class for better debugging experience
+ internal class SystemThreadingTasks_TaskDebugView
+ {
+ private Task m_task;
+
+ public SystemThreadingTasks_TaskDebugView(Task task)
+ {
+ m_task = task;
+ }
+
+ public object AsyncState { get { return m_task.AsyncState; } }
+ public TaskCreationOptions CreationOptions { get { return m_task.CreationOptions; } }
+ public Exception Exception { get { return m_task.Exception; } }
+ public int Id { get { return m_task.Id; } }
+ public bool CancellationPending { get { return (m_task.Status == TaskStatus.WaitingToRun) && m_task.CancellationToken.IsCancellationRequested; } }
+ public TaskStatus Status { get { return m_task.Status; } }
+ }
+
+ // Special purpose derivation of Task that supports limited replication through
+ // overriding the ShouldReplicate() method. This is used by the Parallel.For/ForEach
+ // methods.
+ internal class ParallelForReplicatingTask : Task
+ {
+ // Member variables
+ private int m_replicationDownCount; // downcounter to control replication
+
+ //
+ // Constructors
+ //
+
+ [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable
+ internal ParallelForReplicatingTask(
+ ParallelOptions parallelOptions, Action action, TaskCreationOptions creationOptions, InternalTaskOptions internalOptions)
+ : base(action, null, Task.InternalCurrent, default(CancellationToken), creationOptions, internalOptions | InternalTaskOptions.SelfReplicating, null)
+ {
+ // Compute the down count based on scheduler/DOP info in parallelOptions.
+ m_replicationDownCount = parallelOptions.EffectiveMaxConcurrencyLevel;
+
+ StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
+ PossiblyCaptureContext(ref stackMark);
+ }
+
+
+ // Controls degree of replication. If downcounter is initialized to -1, then
+ // replication will be allowed to "run wild". Otherwise, this method decrements
+ // the downcounter each time it is called, calling false when it is called with
+ // a zero downcounter. This method returning false effectively ends the replication
+ // of the associated ParallelForReplicatingTask.
+ internal override bool ShouldReplicate()
+ {
+ if (m_replicationDownCount == -1) return true; // "run wild"
+
+ if (m_replicationDownCount > 0) // Decrement and return true if not called with 0 downcount
+ {
+ m_replicationDownCount--;
+ return true;
+ }
+
+ return false; // We're done replicating
+ }
+
+ internal override Task CreateReplicaTask(Action<object> taskReplicaDelegate, Object stateObject, Task parentTask, TaskScheduler taskScheduler,
+ TaskCreationOptions creationOptionsForReplica, InternalTaskOptions internalOptionsForReplica)
+ {
+ return new ParallelForReplicaTask(taskReplicaDelegate, stateObject, parentTask, taskScheduler,
+ creationOptionsForReplica, internalOptionsForReplica);
+ }
+
+
+ }
+
+ internal class ParallelForReplicaTask : Task
+ {
+ internal object m_stateForNextReplica; // some replicas may quit prematurely, in which case they will use this variable
+ // to save state they want to be picked up by the next replica queued to the same thread
+
+ internal object m_stateFromPreviousReplica; // some replicas may quit prematurely, in which case they will use this variable
+ // to save state they want to be picked up by the next replica queued to the same thread
+
+ internal Task m_handedOverChildReplica; // some replicas may quit prematurely, in which case they will use this variable
+ // to hand over the child replica they had queued to the next task that will replace them
+
+ internal ParallelForReplicaTask(Action<object> taskReplicaDelegate, Object stateObject, Task parentTask, TaskScheduler taskScheduler,
+ TaskCreationOptions creationOptionsForReplica, InternalTaskOptions internalOptionsForReplica) :
+ base(taskReplicaDelegate, stateObject, parentTask, default(CancellationToken), creationOptionsForReplica, internalOptionsForReplica, taskScheduler)
+ {
+ }
+
+ // Allows internal deriving classes to support replicas that exit prematurely and want to pass on state to the next replica
+ internal override Object SavedStateForNextReplica
+ {
+ get { return m_stateForNextReplica; }
+
+ set { m_stateForNextReplica = value; }
+ }
+
+ // Allows internal deriving classes to support replicas that exit prematurely and want to pass on state to the next replica
+ internal override Object SavedStateFromPreviousReplica
+ {
+ get { return m_stateFromPreviousReplica; }
+
+ set { m_stateFromPreviousReplica = value; }
+ }
+
+ // Allows internal deriving classes to support replicas that exit prematurely and want to hand over the child replica that they
+ // had queued, so that the replacement replica can work with that child task instead of queuing up yet another one
+ internal override Task HandedOverChildReplica
+ {
+ get { return m_handedOverChildReplica; }
+
+ set { m_handedOverChildReplica = value; }
+ }
+ }
+
+ /// <summary>
+ /// Specifies flags that control optional behavior for the creation and execution of tasks.
+ /// </summary>
+ // NOTE: These options are a subset of TaskContinuationsOptions, thus before adding a flag check it is
+ // not already in use.
+ [Flags]
+ [Serializable]
+ public enum TaskCreationOptions
+ {
+ /// <summary>
+ /// Specifies that the default behavior should be used.
+ /// </summary>
+ None = 0x0,
+
+ /// <summary>
+ /// A hint to a <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> to schedule a
+ /// task in as fair a manner as possible, meaning that tasks scheduled sooner will be more likely to
+ /// be run sooner, and tasks scheduled later will be more likely to be run later.
+ /// </summary>
+ PreferFairness = 0x01,
+
+ /// <summary>
+ /// Specifies that a task will be a long-running, course-grained operation. It provides a hint to the
+ /// <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> that oversubscription may be
+ /// warranted.
+ /// </summary>
+ LongRunning = 0x02,
+
+ /// <summary>
+ /// Specifies that a task is attached to a parent in the task hierarchy.
+ /// </summary>
+ AttachedToParent = 0x04,
+
+ /// <summary>
+ /// Specifies that an InvalidOperationException will be thrown if an attempt is made to attach a child task to the created task.
+ /// </summary>
+ DenyChildAttach = 0x08,
+
+ /// <summary>
+ /// Prevents the ambient scheduler from being seen as the current scheduler in the created task. This means that operations
+ /// like StartNew or ContinueWith that are performed in the created task will see TaskScheduler.Default as the current scheduler.
+ /// </summary>
+ HideScheduler = 0x10,
+
+ // 0x20 is already being used in TaskContinuationOptions
+
+ /// <summary>
+ /// Forces continuations added to the current task to be executed asynchronously.
+ /// This option has precedence over TaskContinuationOptions.ExecuteSynchronously
+ /// </summary>
+ RunContinuationsAsynchronously = 0x40
+ }
+
+
+ /// <summary>
+ /// Task creation flags which are only used internally.
+ /// </summary>
+ [Flags]
+ [Serializable]
+ internal enum InternalTaskOptions
+ {
+ /// <summary> Specifies "No internal task options" </summary>
+ None,
+
+ /// <summary>Used to filter out internal vs. public task creation options.</summary>
+ InternalOptionsMask = 0x0000FF00,
+
+ ChildReplica = 0x0100,
+ ContinuationTask = 0x0200,
+ PromiseTask = 0x0400,
+ SelfReplicating = 0x0800,
+
+ /// <summary>
+ /// Store the presence of TaskContinuationOptions.LazyCancellation, since it does not directly
+ /// translate into any TaskCreationOptions.
+ /// </summary>
+ LazyCancellation = 0x1000,
+
+ /// <summary>Specifies that the task will be queued by the runtime before handing it over to the user.
+ /// This flag will be used to skip the cancellationtoken registration step, which is only meant for unstarted tasks.</summary>
+ QueuedByRuntime = 0x2000,
+
+ /// <summary>
+ /// Denotes that Dispose should be a complete nop for a Task. Used when constructing tasks that are meant to be cached/reused.
+ /// </summary>
+ DoNotDispose = 0x4000
+ }
+
+ /// <summary>
+ /// Specifies flags that control optional behavior for the creation and execution of continuation tasks.
+ /// </summary>
+ [Flags]
+ [Serializable]
+ public enum TaskContinuationOptions
+ {
+ /// <summary>
+ /// Default = "Continue on any, no task options, run asynchronously"
+ /// Specifies that the default behavior should be used. Continuations, by default, will
+ /// be scheduled when the antecedent task completes, regardless of the task's final <see
+ /// cref="System.Threading.Tasks.TaskStatus">TaskStatus</see>.
+ /// </summary>
+ None = 0,
+
+ // These are identical to their meanings and values in TaskCreationOptions
+
+ /// <summary>
+ /// A hint to a <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> to schedule a
+ /// task in as fair a manner as possible, meaning that tasks scheduled sooner will be more likely to
+ /// be run sooner, and tasks scheduled later will be more likely to be run later.
+ /// </summary>
+ PreferFairness = 0x01,
+
+ /// <summary>
+ /// Specifies that a task will be a long-running, course-grained operation. It provides
+ /// a hint to the <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> that
+ /// oversubscription may be warranted.
+ /// </summary>
+ LongRunning = 0x02,
+ /// <summary>
+ /// Specifies that a task is attached to a parent in the task hierarchy.
+ /// </summary>
+ AttachedToParent = 0x04,
+
+ /// <summary>
+ /// Specifies that an InvalidOperationException will be thrown if an attempt is made to attach a child task to the created task.
+ /// </summary>
+ DenyChildAttach = 0x08,
+ /// <summary>
+ /// Prevents the ambient scheduler from being seen as the current scheduler in the created task. This means that operations
+ /// like StartNew or ContinueWith that are performed in the created task will see TaskScheduler.Default as the current scheduler.
+ /// </summary>
+ HideScheduler = 0x10,
+
+ /// <summary>
+ /// In the case of continuation cancellation, prevents completion of the continuation until the antecedent has completed.
+ /// </summary>
+ LazyCancellation = 0x20,
+
+ RunContinuationsAsynchronously = 0x40,
+
+ // These are specific to continuations
+
+ /// <summary>
+ /// Specifies that the continuation task should not be scheduled if its antecedent ran to completion.
+ /// This option is not valid for multi-task continuations.
+ /// </summary>
+ NotOnRanToCompletion = 0x10000,
+ /// <summary>
+ /// Specifies that the continuation task should not be scheduled if its antecedent threw an unhandled
+ /// exception. This option is not valid for multi-task continuations.
+ /// </summary>
+ NotOnFaulted = 0x20000,
+ /// <summary>
+ /// Specifies that the continuation task should not be scheduled if its antecedent was canceled. This
+ /// option is not valid for multi-task continuations.
+ /// </summary>
+ NotOnCanceled = 0x40000,
+ /// <summary>
+ /// Specifies that the continuation task should be scheduled only if its antecedent ran to
+ /// completion. This option is not valid for multi-task continuations.
+ /// </summary>
+ OnlyOnRanToCompletion = NotOnFaulted | NotOnCanceled,
+ /// <summary>
+ /// Specifies that the continuation task should be scheduled only if its antecedent threw an
+ /// unhandled exception. This option is not valid for multi-task continuations.
+ /// </summary>
+ OnlyOnFaulted = NotOnRanToCompletion | NotOnCanceled,
+ /// <summary>
+ /// Specifies that the continuation task should be scheduled only if its antecedent was canceled.
+ /// This option is not valid for multi-task continuations.
+ /// </summary>
+ OnlyOnCanceled = NotOnRanToCompletion | NotOnFaulted,
+ /// <summary>
+ /// Specifies that the continuation task should be executed synchronously. With this option
+ /// specified, the continuation will be run on the same thread that causes the antecedent task to
+ /// transition into its final state. If the antecedent is already complete when the continuation is
+ /// created, the continuation will run on the thread creating the continuation. Only very
+ /// short-running continuations should be executed synchronously.
+ /// </summary>
+ ExecuteSynchronously = 0x80000
+ }
+
+ /// <summary>
+ /// Internal helper class to keep track of stack depth and decide whether we should inline or not.
+ /// </summary>
+ internal class StackGuard
+ {
+ // current thread's depth of nested inline task executions
+ private int m_inliningDepth = 0;
+
+ // For relatively small inlining depths we don't want to get into the business of stack probing etc.
+ // This clearly leaves a window of opportunity for the user code to SO. However a piece of code
+ // that can SO in 20 inlines on a typical 1MB stack size probably needs to be revisited anyway.
+ private const int MAX_UNCHECKED_INLINING_DEPTH = 20;
+
+#if !FEATURE_CORECLR
+
+ private UInt64 m_lastKnownWatermark;
+ private static int s_pageSize;
+
+ // We are conservative here. We assume that the platform needs a whole 64KB to
+ // respond to stack overflow. This means that for very small stacks (e.g. 128KB)
+ // we'll fail a lot of stack checks incorrectly.
+ private const long STACK_RESERVED_SPACE = 4096 * 16;
+
+#endif // !FEATURE_CORECLR
+
+ /// <summary>
+ /// This method needs to be called before attempting inline execution on the current thread.
+ /// If false is returned, it means we are too close to the end of the stack and should give up inlining.
+ /// Each call to TryBeginInliningScope() that returns true must be matched with a
+ /// call to EndInliningScope() regardless of whether inlining actually took place.
+ /// </summary>
+ [SecuritySafeCritical]
+ internal bool TryBeginInliningScope()
+ {
+ // If we're still under the 'safe' limit we'll just skip the stack probe to save p/invoke calls
+ if (m_inliningDepth < MAX_UNCHECKED_INLINING_DEPTH || CheckForSufficientStack())
+ {
+ m_inliningDepth++;
+ return true;
+ }
+ else
+ return false;
+ }
+
+ /// <summary>
+ /// This needs to be called once for each previous successful TryBeginInliningScope() call after
+ /// inlining related logic runs.
+ /// </summary>
+ internal void EndInliningScope()
+ {
+ m_inliningDepth--;
+ Contract.Assert(m_inliningDepth >= 0, "Inlining depth count should never go negative.");
+
+ // do the right thing just in case...
+ if (m_inliningDepth < 0) m_inliningDepth = 0;
+ }
+
+ [SecurityCritical]
+ private unsafe bool CheckForSufficientStack()
+ {
+#if FEATURE_CORECLR
+ return RuntimeHelpers.TryEnsureSufficientExecutionStack();
+#else
+ // see if we already have the system page size info recorded
+ int pageSize = s_pageSize;
+ if (pageSize == 0)
+ {
+ // If not we need to query it from GetSystemInfo()
+ // Note that this happens only once for the process lifetime
+ Win32Native.SYSTEM_INFO sysInfo = new Win32Native.SYSTEM_INFO();
+ Win32Native.GetSystemInfo(ref sysInfo);
+
+ s_pageSize = pageSize = sysInfo.dwPageSize;
+ }
+
+ Win32Native.MEMORY_BASIC_INFORMATION stackInfo = new Win32Native.MEMORY_BASIC_INFORMATION();
+
+ // We subtract one page for our request. VirtualQuery rounds UP to the next page.
+ // Unfortunately, the stack grows down. If we're on the first page (last page in the
+ // VirtualAlloc), we'll be moved to the next page, which is off the stack!
+
+ UIntPtr currentAddr = new UIntPtr(&stackInfo - pageSize);
+ UInt64 current64 = currentAddr.ToUInt64();
+
+ // Check whether we previously recorded a deeper stack than where we currently are,
+ // If so we don't need to do the P/Invoke to VirtualQuery
+ if (m_lastKnownWatermark != 0 && current64 > m_lastKnownWatermark)
+ return true;
+
+ // Actual stack probe. P/Invoke to query for the current stack allocation information.
+ Win32Native.VirtualQuery(currentAddr.ToPointer(), ref stackInfo, (UIntPtr)(sizeof(Win32Native.MEMORY_BASIC_INFORMATION)));
+
+ // If the current address minus the base (remember: the stack grows downward in the
+ // address space) is greater than the number of bytes requested plus the reserved
+ // space at the end, the request has succeeded.
+
+ if ((current64 - ((UIntPtr)stackInfo.AllocationBase).ToUInt64()) > STACK_RESERVED_SPACE)
+ {
+ m_lastKnownWatermark = current64;
+ return true;
+ }
+
+ return false;
+#endif
+ }
+ }
+
+ // Special internal struct that we use to signify that we are not interested in
+ // a Task<VoidTaskResult>'s result.
+ internal struct VoidTaskResult { }
+
+ // Interface to which all completion actions must conform.
+ // This interface allows us to combine functionality and reduce allocations.
+ // For example, see Task.SetOnInvokeMres, and its use in Task.SpinThenBlockingWait().
+ // This code:
+ // ManualResetEvent mres = new ManualResetEventSlim(false, 0);
+ // Action<Task> completionAction = delegate { mres.Set() ; };
+ // AddCompletionAction(completionAction);
+ // gets replaced with this:
+ // SetOnInvokeMres mres = new SetOnInvokeMres();
+ // AddCompletionAction(mres);
+ // For additional examples of where this is used, see internal classes Task.SignalOnInvokeCDE,
+ // Task.WhenAllPromise, Task.WhenAllPromise<T>, TaskFactory.CompleteOnCountdownPromise,
+ // TaskFactory.CompleteOnCountdownPromise<T>, and TaskFactory.CompleteOnInvokePromise.
+ internal interface ITaskCompletionAction
+ {
+ /// <summary>Invoked to run the completion action.</summary>
+ void Invoke(Task completingTask);
+
+ /// <summary>
+ /// Some completion actions are considered internal implementation details of tasks,
+ /// using the continuation mechanism only for performance reasons. Such actions perform
+ /// known quantities and types of work, and can be invoked safely as a continuation even
+ /// if the system wants to prevent arbitrary continuations from running synchronously.
+ /// This should only return false for a limited set of implementations where a small amount
+ /// of work is guaranteed to be performed, e.g. setting a ManualResetEventSlim.
+ /// </summary>
+ bool InvokeMayRunArbitraryCode { get; }
+ }
+
+ // This class encapsulates all "unwrap" logic, and also implements ITaskCompletionAction,
+ // which minimizes the allocations needed for queuing it to its antecedent. This
+ // logic is used by both the Unwrap extension methods and the unwrap-style Task.Run methods.
+ internal sealed class UnwrapPromise<TResult> : Task<TResult>, ITaskCompletionAction
+ {
+ // The possible states for our UnwrapPromise, used by Invoke() to determine which logic to execute
+ private const byte STATE_WAITING_ON_OUTER_TASK = 0; // Invoke() means "process completed outer task"
+ private const byte STATE_WAITING_ON_INNER_TASK = 1; // Invoke() means "process completed inner task"
+ private const byte STATE_DONE = 2; // Invoke() means "something went wrong and we are hosed!"
+
+ // Keep track of our state; initialized to STATE_WAITING_ON_OUTER_TASK in the constructor
+ private byte _state;
+
+ // "Should we check for OperationCanceledExceptions on the outer task and interpret them as proxy cancellation?"
+ // Unwrap() sets this to false, Run() sets it to true.
+ private readonly bool _lookForOce;
+
+ public UnwrapPromise(Task outerTask, bool lookForOce)
+ : base((object)null, outerTask.CreationOptions & TaskCreationOptions.AttachedToParent)
+ {
+ Contract.Requires(outerTask != null, "Expected non-null outerTask");
+ _lookForOce = lookForOce;
+ _state = STATE_WAITING_ON_OUTER_TASK;
+
+ if (AsyncCausalityTracer.LoggingOn)
+ AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, this.Id, "Task.Unwrap", 0);
+
+ if (Task.s_asyncDebuggingEnabled)
+ {
+ AddToActiveTasks(this);
+ }
+
+ // Link ourselves to the outer task.
+ // If the outer task has already completed, take the fast path
+ // of immediately transferring its results or processing the inner task.
+ if (outerTask.IsCompleted)
+ {
+ ProcessCompletedOuterTask(outerTask);
+ }
+ else // Otherwise, process its completion asynchronously.
+ {
+ outerTask.AddCompletionAction(this);
+ }
+ }
+
+ // For ITaskCompletionAction
+ public void Invoke(Task completingTask)
+ {
+ // Check the current stack guard. If we're ok to inline,
+ // process the task, and reset the guard when we're done.
+ var sg = Task.CurrentStackGuard;
+ if (sg.TryBeginInliningScope())
+ {
+ try { InvokeCore(completingTask); }
+ finally { sg.EndInliningScope(); }
+ }
+ // Otherwise, we're too deep on the stack, and
+ // we shouldn't run the continuation chain here, so queue a work
+ // item to call back here to Invoke asynchronously.
+ else InvokeCoreAsync(completingTask);
+ }
+
+ /// <summary>
+ /// Processes the completed task. InvokeCore could be called twice:
+ /// once for the outer task, once for the inner task.
+ /// </summary>
+ /// <param name="completingTask">The completing outer or inner task.</param>
+ private void InvokeCore(Task completingTask)
+ {
+ switch (_state)
+ {
+ case STATE_WAITING_ON_OUTER_TASK:
+ ProcessCompletedOuterTask(completingTask);
+ // We bump the state inside of ProcessCompletedOuterTask because it can also be called from the constructor.
+ break;
+ case STATE_WAITING_ON_INNER_TASK:
+ bool result = TrySetFromTask(completingTask, lookForOce: false);
+ _state = STATE_DONE; // bump the state
+ Contract.Assert(result, "Expected TrySetFromTask from inner task to succeed");
+ break;
+ default:
+ Contract.Assert(false, "UnwrapPromise in illegal state");
+ break;
+ }
+ }
+
+ // Calls InvokeCore asynchronously.
+ [SecuritySafeCritical]
+ private void InvokeCoreAsync(Task completingTask)
+ {
+ // Queue a call to Invoke. If we're so deep on the stack that we're at risk of overflowing,
+ // there's a high liklihood this thread is going to be doing lots more work before
+ // returning to the thread pool (at the very least unwinding through thousands of
+ // stack frames). So we queue to the global queue.
+ ThreadPool.UnsafeQueueUserWorkItem(state =>
+ {
+ // InvokeCore(completingTask);
+ var tuple = (Tuple<UnwrapPromise<TResult>, Task>)state;
+ tuple.Item1.InvokeCore(tuple.Item2);
+ }, Tuple.Create<UnwrapPromise<TResult>, Task>(this, completingTask));
+ }
+
+ /// <summary>Processes the outer task once it's completed.</summary>
+ /// <param name="task">The now-completed outer task.</param>
+ private void ProcessCompletedOuterTask(Task task)
+ {
+ Contract.Requires(task != null && task.IsCompleted, "Expected non-null, completed outer task");
+ Contract.Assert(_state == STATE_WAITING_ON_OUTER_TASK, "We're in the wrong state!");
+
+ // Bump our state before proceeding any further
+ _state = STATE_WAITING_ON_INNER_TASK;
+
+ switch (task.Status)
+ {
+ // If the outer task did not complete successfully, then record the
+ // cancellation/fault information to tcs.Task.
+ case TaskStatus.Canceled:
+ case TaskStatus.Faulted:
+ bool result = TrySetFromTask(task, _lookForOce);
+ Contract.Assert(result, "Expected TrySetFromTask from outer task to succeed");
+ break;
+
+ // Otherwise, process the inner task it returned.
+ case TaskStatus.RanToCompletion:
+ var taskOfTaskOfTResult = task as Task<Task<TResult>>; // it's either a Task<Task> or Task<Task<TResult>>
+ ProcessInnerTask(taskOfTaskOfTResult != null ?
+ taskOfTaskOfTResult.Result : ((Task<Task>)task).Result);
+ break;
+ }
+ }
+
+ /// <summary>Transfer the completion status from "task" to ourself.</summary>
+ /// <param name="task">The source task whose results should be transfered to <paramref name="promise"/>.</param>
+ /// <param name="lookForOce">Whether or not to look for OperationCanceledExceptions in task's exceptions if it faults.</param>
+ /// <returns>true if the transfer was successful; otherwise, false.</returns>
+ private bool TrySetFromTask(Task task, bool lookForOce)
+ {
+ Contract.Requires(task != null && task.IsCompleted, "TrySetFromTask: Expected task to have completed.");
+
+ if (AsyncCausalityTracer.LoggingOn)
+ AsyncCausalityTracer.TraceOperationRelation(CausalityTraceLevel.Important, this.Id, CausalityRelation.Join);
+
+ bool result = false;
+ switch (task.Status)
+ {
+ case TaskStatus.Canceled:
+ result = TrySetCanceled(task.CancellationToken, task.GetCancellationExceptionDispatchInfo());
+ break;
+
+ case TaskStatus.Faulted:
+ var edis = task.GetExceptionDispatchInfos();
+ ExceptionDispatchInfo oceEdi;
+ OperationCanceledException oce;
+ if (lookForOce && edis.Count > 0 &&
+ (oceEdi = edis[0]) != null &&
+ (oce = oceEdi.SourceException as OperationCanceledException) != null)
+ {
+ result = TrySetCanceled(oce.CancellationToken, oceEdi);
+ }
+ else
+ {
+ result = TrySetException(edis);
+ }
+ break;
+
+ case TaskStatus.RanToCompletion:
+ var taskTResult = task as Task<TResult>;
+
+ if (AsyncCausalityTracer.LoggingOn)
+ AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, this.Id, AsyncCausalityStatus.Completed);
+
+ if (Task.s_asyncDebuggingEnabled)
+ {
+ RemoveFromActiveTasks(this.Id);
+ }
+
+ result = TrySetResult(taskTResult != null ? taskTResult.Result : default(TResult));
+ break;
+ }
+ return result;
+ }
+
+ /// <summary>
+ /// Processes the inner task of a Task{Task} or Task{Task{TResult}},
+ /// transferring the appropriate results to ourself.
+ /// </summary>
+ /// <param name="task">The inner task returned by the task provided by the user.</param>
+ private void ProcessInnerTask(Task task)
+ {
+ // If the inner task is null, the proxy should be canceled.
+ if (task == null)
+ {
+ TrySetCanceled(default(CancellationToken));
+ _state = STATE_DONE; // ... and record that we are done
+ }
+
+ // Fast path for if the inner task is already completed
+ else if (task.IsCompleted)
+ {
+ TrySetFromTask(task, lookForOce: false);
+ _state = STATE_DONE; // ... and record that we are done
+ }
+
+ // The inner task exists but is not yet complete, so when it does complete,
+ // take some action to set our completion state.
+ else
+ {
+ task.AddCompletionAction(this);
+ // We'll record that we are done when Invoke() is called.
+ }
+ }
+
+ public bool InvokeMayRunArbitraryCode { get { return true; } }
+ }
+
+}