diff options
Diffstat (limited to 'src/mscorlib/src/System/Threading/Tasks/Task.cs')
-rw-r--r-- | src/mscorlib/src/System/Threading/Tasks/Task.cs | 511 |
1 files changed, 260 insertions, 251 deletions
diff --git a/src/mscorlib/src/System/Threading/Tasks/Task.cs b/src/mscorlib/src/System/Threading/Tasks/Task.cs index 7013c5c5e0..8e2e6a4cb0 100644 --- a/src/mscorlib/src/System/Threading/Tasks/Task.cs +++ b/src/mscorlib/src/System/Threading/Tasks/Task.cs @@ -29,7 +29,6 @@ using System.Diagnostics.Tracing; namespace System.Threading.Tasks { - /// <summary> /// Utility class for allocating structs as heap variables /// </summary> @@ -41,7 +40,6 @@ namespace System.Threading.Tasks { this.Value = value; } - } /// <summary> @@ -271,7 +269,7 @@ namespace System.Threading.Tasks // 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; + internal Task m_parent; /// <summary> /// Sets the internal completion event. @@ -569,8 +567,8 @@ namespace System.Threading.Tasks #if DEBUG // Check the validity of internalOptions - int illegalInternalOptions = - (int) (internalOptions & + int illegalInternalOptions = + (int)(internalOptions & ~(InternalTaskOptions.PromiseTask | InternalTaskOptions.ContinuationTask | InternalTaskOptions.LazyCancellation | @@ -581,27 +579,26 @@ namespace System.Threading.Tasks // Assign options to m_stateAndOptionsFlag. Debug.Assert(m_stateFlags == 0, "TaskConstructorCore: non-zero m_stateFlags"); Debug.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 + int tmpFlags = (int)creationOptions | (int)internalOptions; // one write to the volatile m_stateFlags instead of two when setting the above options + m_stateFlags = m_action == null || (internalOptions & InternalTaskOptions.ContinuationTask) != 0 ? + tmpFlags | TASK_STATE_WAITINGFORACTIVATION : + tmpFlags; // 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) - ) + ContingentProperties props = m_contingentProperties; + if (props != null) { - parent.AddNewChild(); + Task parent = props.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 @@ -716,14 +713,7 @@ namespace System.Threading.Tasks } // 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); - } - } + internal TaskCreationOptions Options => OptionsMethod(m_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. @@ -737,11 +727,16 @@ namespace System.Threading.Tasks // 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. + int oldFlags = m_stateFlags; + return + (oldFlags & illegalBits) == 0 && + (Interlocked.CompareExchange(ref m_stateFlags, oldFlags | newBits, oldFlags) == oldFlags || + AtomicStateUpdateSlow(newBits, illegalBits)); + } - SpinWait sw = new SpinWait(); + private bool AtomicStateUpdateSlow(int newBits, int illegalBits) + { + var sw = new SpinWait(); do { int oldFlags = m_stateFlags; @@ -900,7 +895,6 @@ namespace System.Threading.Tasks return AtomicStateUpdate(TASK_STATE_STARTED, TASK_STATE_CANCELED | TASK_STATE_STARTED); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] internal bool FireTaskScheduledIfNeeded(TaskScheduler ts) { var etwLog = TplEtwProvider.Log; @@ -1789,7 +1783,7 @@ namespace System.Threading.Tasks catch (ThreadAbortException tae) { AddException(tae); - FinishThreadAbortedTask(delegateRan:false); + FinishThreadAbortedTask(delegateRan: false); } catch (Exception e) { @@ -2041,32 +2035,45 @@ namespace System.Threading.Tasks /// <summary> /// Signals completion of this particular task. /// - /// The bUserDelegateExecuted parameter indicates whether this Finish() call comes following the + /// The userDelegateExecute 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 + /// If userDelegateExecute 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) + internal void Finish(bool userDelegateExecute) + { + if (m_contingentProperties == null) + { + FinishStageTwo(); + } + else + { + FinishSlow(userDelegateExecute); + } + } + + private void FinishSlow(bool userDelegateExecute) { - if (!bUserDelegateExecuted) + Debug.Assert(userDelegateExecute || m_contingentProperties != null); + + if (!userDelegateExecute) { // delegate didn't execute => no children. We can safely call the remaining finish stages FinishStageTwo(); } else { - var props = Volatile.Read(ref m_contingentProperties); + ContingentProperties props = m_contingentProperties; - if (props == null || // no contingent properties means no children, so it's safe to complete ourselves - (props.m_completionCountdown == 1) || - // Count of 1 => either all children finished, or there were none. Safe to complete ourselves - // without paying the price of an Interlocked.Decrement. + // Count of 1 => either all children finished, or there were none. Safe to complete ourselves + // without paying the price of an Interlocked.Decrement. + if ((props.m_completionCountdown == 1) || 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. + // and we could be racing with one of them to call FinishStageTwo(). + // So whoever does the final Interlocked.Dec is responsible to finish. { FinishStageTwo(); } @@ -2086,8 +2093,7 @@ namespace System.Threading.Tasks // 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; - + List<Task> exceptionalChildren = props.m_exceptionalChildren; if (exceptionalChildren != null) { lock (exceptionalChildren) @@ -2106,12 +2112,17 @@ namespace System.Threading.Tasks /// 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() + private 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. + + ContingentProperties cp = Volatile.Read(ref m_contingentProperties); + if (cp != null) + { + AddExceptionsFromChildren(cp); + } + int completionState; if (ExceptionRecorded) { @@ -2160,7 +2171,7 @@ namespace System.Threading.Tasks // 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); + cp = Volatile.Read(ref m_contingentProperties); // need to re-read after updating state if (cp != null) { cp.SetCompleted(); @@ -2187,6 +2198,17 @@ namespace System.Threading.Tasks m_action = null; // Notify parent if this was an attached task + if (m_contingentProperties != null) + { + NotifyParentIfPotentiallyAttachedTask(); + } + + // Activate continuations (if any). + FinishContinuations(); + } + + internal void NotifyParentIfPotentiallyAttachedTask() + { Task parent = m_contingentProperties?.m_parent; if (parent != null && ((parent.CreationOptions & TaskCreationOptions.DenyChildAttach) == 0) @@ -2194,9 +2216,6 @@ namespace System.Threading.Tasks { parent.ProcessChildCompletion(this); } - - // Activate continuations (if any). - FinishContinuations(); } /// <summary> @@ -2232,7 +2251,6 @@ namespace System.Threading.Tasks tmp.Add(childTask); } } - } if (Interlocked.Decrement(ref props.m_completionCountdown) == 0) @@ -2248,14 +2266,15 @@ namespace System.Threading.Tasks /// 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() + internal void AddExceptionsFromChildren(ContingentProperties props) { + Debug.Assert(props != null); + // 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; + List<Task> exceptionalChildren = props.m_exceptionalChildren; if (exceptionalChildren != null) { @@ -2310,40 +2329,13 @@ namespace System.Threading.Tasks Finish(delegateRan); } - - /// <summary> - /// Executes the task. This method will only be called once, and handles any necessary exception marshaling. - /// </summary> - private void Execute() - { - try - { - InnerInvoke(); - } - catch (ThreadAbortException tae) - { - // 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(delegateRan: true); - } - catch (Exception exn) - { - // Record this exception in the task's exception list - HandleException(exn); - } - } - /// <summary> /// IThreadPoolWorkItem override, which is the entry function for this task when the TP scheduler decides to run it. /// /// </summary> void IThreadPoolWorkItem.ExecuteWorkItem() { - ExecuteEntry(false); + ExecuteEntryUnsafe(); } /// <summary> @@ -2357,46 +2349,58 @@ namespace System.Threading.Tasks if (!IsCompleted) { HandleException(tae); - FinishThreadAbortedTask(delegateRan:false); + FinishThreadAbortedTask(delegateRan: 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 ThreadPool scheduler doesn't need this. </param> - internal bool ExecuteEntry(bool bPreventDoubleExecution) + internal bool ExecuteEntry() { - if (bPreventDoubleExecution) + // 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) + int previousState = 0; + if (!AtomicStateUpdate(TASK_STATE_DELEGATE_INVOKED, + TASK_STATE_DELEGATE_INVOKED | TASK_STATE_COMPLETED_MASK, + ref previousState) && (previousState & TASK_STATE_CANCELED) == 0) { - int previousState = 0; + // This task has already been invoked. Don't invoke it again. + return false; + } - // 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; - } + if (!IsCancellationRequested & !IsCanceled) + { + ExecuteWithThreadLocal(ref t_currentTask); } else { - // Remember that we started running the task delegate. - m_stateFlags |= TASK_STATE_DELEGATE_INVOKED; + ExecuteEntryCancellationRequestedOrCanceled(); } - if (!IsCancellationRequested && !IsCanceled) + return true; + } + + internal void ExecuteEntryUnsafe() // used instead of ExecuteEntry() when we don't have to worry about double-execution prevent + { + // Remember that we started running the task delegate. + m_stateFlags |= TASK_STATE_DELEGATE_INVOKED; + + if (!IsCancellationRequested & !IsCanceled) { ExecuteWithThreadLocal(ref t_currentTask); } - else if (!IsCanceled) + else + { + ExecuteEntryCancellationRequestedOrCanceled(); + } + } + + internal void ExecuteEntryCancellationRequestedOrCanceled() + { + if (!IsCanceled) { int prevState = Interlocked.Exchange(ref m_stateFlags, m_stateFlags | TASK_STATE_CANCELED); if ((prevState & TASK_STATE_CANCELED) == 0) @@ -2404,8 +2408,6 @@ namespace System.Threading.Tasks CancellationCleanupLogic(); } } - - return true; } // A trick so we can refer to the TLS slot with a byref. @@ -2429,30 +2431,44 @@ namespace System.Threading.Tasks etwLog.TaskStarted(TaskScheduler.Current.Id, 0, this.Id); } - if (AsyncCausalityTracer.LoggingOn) + bool loggingOn = AsyncCausalityTracer.LoggingOn; + if (loggingOn) AsyncCausalityTracer.TraceSynchronousWorkStart(CausalityTraceLevel.Required, this.Id, CausalitySynchronousWork.Execution); - try { // place the current task into TLS. currentTaskSlot = this; - ExecutionContext ec = CapturedContext; - if (ec == null) + // Execute the task body + try { - // No context, just run the task directly. - Execute(); + ExecutionContext ec = CapturedContext; + if (ec == null) + { + // No context, just run the task directly. + InnerInvoke(); + } + else + { + // Invoke it under the captured ExecutionContext + ExecutionContext.Run(ec, s_ecCallback, this); + } } - else + catch (Exception exn) { - // Run the task. We need a simple shim that converts the - // object back into a Task object, so that we can Execute it. - - ExecutionContext.Run(ec, s_ecCallback, this); + // Record this exception in the task's exception list + HandleException(exn); + if (exn is ThreadAbortException) + { + // 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(delegateRan: true); + } } - if (AsyncCausalityTracer.LoggingOn) + if (loggingOn) AsyncCausalityTracer.TraceSynchronousWorkCompletion(CausalityTraceLevel.Required, CausalitySynchronousWork.Execution); Finish(true); @@ -2460,7 +2476,7 @@ namespace System.Threading.Tasks finally { currentTaskSlot = previousTask; - + // ETW event for Task Completed if (etwIsEnabled) { @@ -2476,7 +2492,7 @@ namespace System.Threading.Tasks } } - private static readonly ContextCallback s_ecCallback = obj => ((Task)obj).Execute(); + private static readonly ContextCallback s_ecCallback = obj => ((Task)obj).InnerInvoke(); /// <summary> /// The actual code which invokes the body of the task. This can be overriden in derived types. @@ -2973,7 +2989,6 @@ namespace System.Threading.Tasks { Thread.SpinWait(PlatformHelper.ProcessorCount * (4 << i)); } - } return IsCompleted; @@ -3031,12 +3046,10 @@ namespace System.Threading.Tasks // 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) @@ -3125,7 +3138,7 @@ namespace System.Threading.Tasks oce = edi.SourceException as OperationCanceledException; Debug.Assert(oce != null, "Expected EDI to contain an OCE"); } - Debug.Assert(oce.CancellationToken == tokenToRecord, + Debug.Assert(oce.CancellationToken == tokenToRecord, "Expected OCE's token to match the provided token."); #endif AddException(cancellationException, representsCancellation: true); @@ -3191,7 +3204,18 @@ namespace System.Threading.Tasks { // 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. + // Then if we grabbed any continuations, run them. object continuationObject = Interlocked.Exchange(ref m_continuationObject, s_taskCompletionSentinel); + if (continuationObject != null) + { + RunContinuations(continuationObject); + } + } + + private void RunContinuations(object continuationObject) // separated out of FinishContinuations to enable it to be inlined + { + Debug.Assert(continuationObject != null); + TplEtwProvider etw = TplEtwProvider.Log; bool tplEtwProviderLoggingEnabled = etw.IsEnabled(); if (tplEtwProviderLoggingEnabled) @@ -3199,136 +3223,125 @@ namespace System.Threading.Tasks 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); + 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)); + // 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-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) + // Handle the single-ITaskCompletionAction case + ITaskCompletionAction singleTaskCompletionAction = continuationObject as ITaskCompletionAction; + if (singleTaskCompletionAction != null) + { + if (bCanInlineContinuations || !singleTaskCompletionAction.InvokeMayRunArbitraryCode) { - if (bCanInlineContinuations || !singleTaskCompletionAction.InvokeMayRunArbitraryCode) - { - singleTaskCompletionAction.Invoke(this); - } - else - { - ThreadPool.UnsafeQueueCustomWorkItem(new CompletionActionInvoker(singleTaskCompletionAction, this), forceGlobal: false); - } - LogFinishCompletionNotification(); - return; + singleTaskCompletionAction.Invoke(this); } - - // Handle the single-TaskContinuation case - TaskContinuation singleTaskContinuation = continuationObject as TaskContinuation; - if (singleTaskContinuation != null) + else { - singleTaskContinuation.Run(this, bCanInlineContinuations); - LogFinishCompletionNotification(); - return; + ThreadPool.UnsafeQueueCustomWorkItem(new CompletionActionInvoker(singleTaskCompletionAction, this), forceGlobal: false); } + LogFinishCompletionNotification(); + return; + } - // Not a single; attempt to cast as list - List<object> continuations = continuationObject as List<object>; + // Handle the single-TaskContinuation case + TaskContinuation singleTaskContinuation = continuationObject as TaskContinuation; + if (singleTaskContinuation != null) + { + singleTaskContinuation.Run(this, bCanInlineContinuations); + LogFinishCompletionNotification(); + return; + } - if (continuations == null) - { - LogFinishCompletionNotification(); - return; // Not a single or a list; just return - } + // Not a single; it must be a list. + List<object> continuations = (List<object>)continuationObject; - // - // Begin processing of continuation list - // + // + // Begin processing of continuation list + // - // Wait for any concurrent adds or removes to be retired - lock (continuations) { } - int continuationCount = continuations.Count; + // 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++) + // 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) { - // 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) { - if (tplEtwProviderLoggingEnabled) - { - etw.RunningContinuationList(Id, i, tc); - } - continuations[i] = null; // so that we can skip this later - tc.Run(this, bCanInlineContinuations); + 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++) + // ... 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) { - 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); - } + 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) + // 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) { - AwaitTaskContinuation.RunOrScheduleAction(ad, bCanInlineContinuations, ref t_currentTask); + // 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 { - // If it's a TaskContinuation object of some kind, invoke it. - TaskContinuation tc = currentContinuation as TaskContinuation; - if (tc != null) + Debug.Assert(currentContinuation is ITaskCompletionAction, "Expected continuation element to be Action, TaskContinuation, or ITaskContinuationAction"); + var action = (ITaskCompletionAction)currentContinuation; + + if (bCanInlineContinuations || !action.InvokeMayRunArbitraryCode) { - // We know that this is a synchronous continuation because the - // asynchronous ones have been weeded out - tc.Run(this, bCanInlineContinuations); + action.Invoke(this); } - // Otherwise, it must be an ITaskCompletionAction, so invoke it. else { - Debug.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); - } + ThreadPool.UnsafeQueueCustomWorkItem(new CompletionActionInvoker(action, this), forceGlobal: false); } } } - - LogFinishCompletionNotification(); } + + LogFinishCompletionNotification(); } private void LogFinishCompletionNotification() @@ -4140,29 +4153,29 @@ namespace System.Threading.Tasks out InternalTaskOptions internalOptions) { // This is used a couple of times below - TaskContinuationOptions NotOnAnything = + const TaskContinuationOptions NotOnAnything = TaskContinuationOptions.NotOnCanceled | TaskContinuationOptions.NotOnFaulted | TaskContinuationOptions.NotOnRanToCompletion; - TaskContinuationOptions creationOptionsMask = + const TaskContinuationOptions CreationOptionsMask = TaskContinuationOptions.PreferFairness | TaskContinuationOptions.LongRunning | TaskContinuationOptions.DenyChildAttach | TaskContinuationOptions.HideScheduler | - TaskContinuationOptions.AttachedToParent| + TaskContinuationOptions.AttachedToParent | TaskContinuationOptions.RunContinuationsAsynchronously; // Check that LongRunning and ExecuteSynchronously are not specified together - TaskContinuationOptions illegalMask = TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.LongRunning; - if ((continuationOptions & illegalMask) == illegalMask) + const 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 | + ~(CreationOptionsMask | NotOnAnything | TaskContinuationOptions.LazyCancellation | TaskContinuationOptions.ExecuteSynchronously)) != 0) { ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.continuationOptions); @@ -4175,14 +4188,12 @@ namespace System.Threading.Tasks } // This passes over all but LazyCancellation, which has no representation in TaskCreationOptions - creationOptions = (TaskCreationOptions)(continuationOptions & creationOptionsMask); - - // internalOptions has at least ContinuationTask ... - internalOptions = InternalTaskOptions.ContinuationTask; + creationOptions = (TaskCreationOptions)(continuationOptions & CreationOptionsMask); - // ... and possibly LazyCancellation - if ((continuationOptions & TaskContinuationOptions.LazyCancellation) != 0) - internalOptions |= InternalTaskOptions.LazyCancellation; + // internalOptions has at least ContinuationTask and possibly LazyCancellation + internalOptions = (continuationOptions & TaskContinuationOptions.LazyCancellation) != 0 ? + InternalTaskOptions.ContinuationTask | InternalTaskOptions.LazyCancellation : + InternalTaskOptions.ContinuationTask; } @@ -4417,7 +4428,6 @@ namespace System.Threading.Tasks { // null out that TaskContinuation entry, which will be interpreted as "to be cleaned up" continuationsLocalListRef[index] = null; - } } } @@ -4500,7 +4510,6 @@ namespace System.Threading.Tasks } return WaitAll(tasks, (int)totalMilliseconds); - } /// <summary> @@ -5020,6 +5029,10 @@ namespace System.Threading.Tasks signaledTaskIndex = Array.IndexOf(tasks, firstCompleted.Result); Debug.Assert(signaledTaskIndex >= 0); } + else + { + TaskFactory.CommonCWAnyLogicCleanup(firstCompleted); + } } // We need to prevent the tasks array from being GC'ed until we come out of the wait. @@ -5103,7 +5116,7 @@ namespace System.Threading.Tasks Debug.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> @@ -5112,7 +5125,7 @@ namespace System.Threading.Tasks { 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> @@ -5122,7 +5135,7 @@ namespace System.Threading.Tasks { return FromCanceled<TResult>(cancellationToken); } - + #endregion #region Run methods @@ -6120,8 +6133,8 @@ namespace System.Threading.Tasks 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); + if (m_continuationObject != this) + return GetDelegatesFromContinuationObject(m_continuationObject); else return null; } @@ -6192,11 +6205,8 @@ namespace System.Threading.Tasks private static Task[] GetActiveTasks() { - return new List<Task>(s_currentActiveTasks.Values).ToArray(); } - - } internal sealed class CompletionActionInvoker : IThreadPoolWorkItem @@ -6715,5 +6725,4 @@ namespace System.Threading.Tasks public bool InvokeMayRunArbitraryCode { get { return true; } } } - } |