C# Task 是什么?返回值如何实现? Wait如何实现

关于Task的API太多了,网上的实例也很多,现在我们来说说Task究竟是个什么东西【task一般用于多线程,它一定与线程有关】,还有它的返回值有事怎么搞的。

首先我们以一个最简单的API开始,TaskFactory的StartNew<TResult>方法,TaskFactory.cs

public Task<TResult> StartNew<TResult>(Func<Object, TResult> function, Object state)
{
    StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
    Task currTask = Task.InternalCurrent;
    return Task<TResult>.StartNew(currTask, function, state, m_defaultCancellationToken,
        m_defaultCreationOptions, InternalTaskOptions.None, GetDefaultScheduler(currTask), ref stackMark);
}
private TaskScheduler GetDefaultScheduler(Task currTask)
{
    if (m_defaultScheduler != null) return m_defaultScheduler;
    else if ((currTask != null)&& ((currTask.CreationOptions & TaskCreationOptions.HideScheduler) == 0))
        return currTask.ExecutingTaskScheduler;
    else return TaskScheduler.Default;
}

可见最终和调用Task<TResult>.StartNew等效的,这里的GetDefaultScheduler返回的是TaskScheduler.Default。TaskScheduler.cs实现如下:

public abstract class TaskScheduler
{
   private static readonly TaskScheduler s_defaultTaskScheduler = new ThreadPoolTaskScheduler();
    public static TaskScheduler Default 
    {
        get
        {
            return s_defaultTaskScheduler;
        }
    }
    internal void InternalQueueTask(Task task)
    {
        Contract.Requires(task != null);
        task.FireTaskScheduledIfNeeded(this);
        this.QueueTask(task);
    }
}

默认的TaskScheduler.Default是ThreadPoolTaskScheduler实例。现在我们看看Task<TResult>的实现 Future.cs

 public class Task<TResult> : Task
 {
   internal static Task<TResult> StartNew(Task parent, Func<object, TResult> function, object state, CancellationToken cancellationToken,
            TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, TaskScheduler scheduler, ref StackCrawlMark stackMark)
    {
        if (function == null)
        {
            throw new ArgumentNullException("function");
        }
        if (scheduler == null)
        {
            throw new ArgumentNullException("scheduler");
        }
        if ((internalOptions & InternalTaskOptions.SelfReplicating) != 0)
        {
            throw new ArgumentOutOfRangeException("creationOptions", Environment.GetResourceString("TaskT_ctor_SelfReplicating"));
        }

        // Create and schedule the future.
        Task<TResult> f = new Task<TResult>(function, state, parent, cancellationToken, creationOptions, internalOptions | InternalTaskOptions.QueuedByRuntime, scheduler, ref stackMark);

        f.ScheduleAndStart(false);
        return f;
    }
   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
        {
            m_taskScheduler.InternalQueueTask(this);
        }
        catch (ThreadAbortException tae)
        {
            AddException(tae);
            FinishThreadAbortedTask(true, false);
        }
        catch (Exception e)
        {
            TaskSchedulerException tse = new TaskSchedulerException(e);
            AddException(tse);
            Finish(false);
            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;
        }
    }
  
        
  internal Task(
        Func<object, TResult> valueSelector, object state, Task parent, CancellationToken cancellationToken,
        TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, TaskScheduler scheduler, ref StackCrawlMark stackMark) :
        this(valueSelector, state, parent, cancellationToken, creationOptions, internalOptions, scheduler)
    {
        PossiblyCaptureContext(ref stackMark);
    }
    internal void PossiblyCaptureContext(ref StackCrawlMark stackMark)
    {
        Contract.Assert(m_contingentProperties == null || m_contingentProperties.m_capturedContext == null, "Captured an ExecutionContext when one was already captured.");
        CapturedContext = ExecutionContext.Capture(ref stackMark, ExecutionContext.CaptureOptions.IgnoreSyncCtx | ExecutionContext.CaptureOptions.OptimizeDefaultCase);
    }
    internal override void InnerInvoke()
    {
        // Invoke the delegate
        Contract.Assert(m_action != null);
        var func = m_action as Func<TResult>;
        if (func != null)
        {
            m_result = func();
            return;
        }
        var funcWithState = m_action as Func<object, TResult>;
        if (funcWithState != null)
        {
            m_result = funcWithState(m_stateObject);
            return;
        }
        Contract.Assert(false, "Invalid m_action in Task<TResult>");
    }

    public TResult Result
    {
        get { return IsWaitNotificationEnabledOrNotRanToCompletion ? GetResultCore(waitCompletionNotification: true) : m_result; }
    }
   
 }

Task<TResult>的StartNew方法首先调用构造函数,在构造函数里面调用PossiblyCaptureContext方法,PossiblyCaptureContext方法调用ExecutionContext.Capture捕获线程上下文,然后回到StartNew方法,调用Task<TResult>。ScheduleAndStart,ScheduleAndStart方法主要是调用TaskScheduler的InternalQueueTask方法TaskScheduler的InternalQueueTask方法主要是调用QueueTask,QueueTask方法在子类被覆盖,这里调用ThreadPoolTaskScheduler的QueueTask方法,ThreadPoolTaskScheduler.cs

internal sealed class ThreadPoolTaskScheduler: TaskScheduler
{
   private static readonly ParameterizedThreadStart s_longRunningThreadWork = new ParameterizedThreadStart(LongRunningThreadWork);

    private static void LongRunningThreadWork(object obj)
    {
        Contract.Requires(obj != null, "TaskScheduler.LongRunningThreadWork: obj is null");
        Task t = obj as Task;
        Contract.Assert(t != null, "TaskScheduler.LongRunningThreadWork: t is null");
        t.ExecuteEntry(false);
    }
    protected internal override void QueueTask(Task task)
    {
        if ((task.Options & TaskCreationOptions.LongRunning) != 0)
        {
            // Run LongRunning tasks on their own dedicated thread.
            Thread thread = new Thread(s_longRunningThreadWork);
            thread.IsBackground = true; // Keep this thread from blocking process shutdown
            thread.Start(task);
        }
        else
        {
            // Normal handling for non-LongRunning tasks.
            bool forceToGlobalQueue = ((task.Options & TaskCreationOptions.PreferFairness) != 0);
            ThreadPool.UnsafeQueueCustomWorkItem(task, forceToGlobalQueue);
        }
    }
}

如果Task是TaskCreationOptions.LongRunning,那么我们新建一个逻辑线程来运行当前的Task,否者放到线程池里面运行

单独的逻辑线程调用s_longRunningThreadWork回调方法【该方法调用task的ExecuteEntry】;如果是线程池那么我们调用Task的ExecuteWorkItem方法【其实还是调用Task的ExecuteEntry】,Task.cs

public class Task : IThreadPoolWorkItem, IAsyncResult, IDisposable
{
    [ThreadStatic]
    internal static Task t_currentTask; 
    internal static Task InternalCurrent
    {
        get { return t_currentTask; }
    }
   void IThreadPoolWorkItem.ExecuteWorkItem()
    {
        ExecuteEntry(false);
    }
    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;
    }
   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 ----
                var callback = s_ecCallback;
                if (callback == null) s_ecCallback = callback = new ContextCallback(ExecutionContextCallback);
#if PFX_LEGACY_3_5
                ExecutionContext.Run(ec, callback, this);
#else
                ExecutionContext.Run(ec, callback, this, true);
#endif
            }

            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);
            }
        }
    }
   private static void ExecutionContextCallback(object obj)
    {
        Task task = obj as Task;
        Contract.Assert(task != null, "expected a task object");
        task.Execute();
    }
    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);
            }
        }
    }
}

到这里Task是什么 就明白了,继续往下看,Task的ExecuteEntry方法主要是调用ExecutionContext.Run(ec, callback, this, true)方法,注意这里的callback是一个ContextCallback实例,里面主要是调用Task的Execute方法,Task的Execute主要是代用InnerInvoke方法,该方法在子类Task<TResult>被重写,InnerInvoke方法里面有2局比较重要【m_result = func() 或者m_result = funcWithState(m_stateObject) 这里才是正真调用我们自己的方法,给返回值赋值】,到这里返回值也就明白了。那么在我们方法结束后需要调用Finish方法来标记方法结束

/// <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 = 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
                }
            }
        }
    }
    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 = m_contingentProperties;
        if (cp != null)
        {
            cp.SetCompleted();
            cp.DeregisterCancellationCallback();
        }

        // ready to run continuations and notify parent.
        FinishStageThree();
    }

    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);
    }

在FinishStageTwo方法后面还会调用FinishStageThree方法,FinishStageThree方法调用FinishContinuations,在FinishContinuations方法里面会获取object continuationObject = Interlocked.Exchange(ref m_continuationObject, s_taskCompletionSentinel);然后再用对应的方法.

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
    if (m_parent != null
         && ((m_parent.CreationOptions & TaskCreationOptions.DenyChildAttach) == 0)
         && (((TaskCreationOptions)(m_stateFlags & OptionsMask)) & TaskCreationOptions.AttachedToParent) != 0)
    {
        m_parent.ProcessChildCompletion(this);
    }

    // Activate continuations (if any).
    FinishContinuations();
}
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.Log.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.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)
            {
                TplEtwProvider.Log.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
            TplEtwProvider.Log.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.Invoke(this);
                    }
                    else
                    {
                        ThreadPool.UnsafeQueueCustomWorkItem(new CompletionActionInvoker(action, this), forceGlobal: false);
                    }
                }
            }
        }

        LogFinishCompletionNotification();
    }
}

当我们访问TTask<TResult> 的Result的时候,如果Task还没有执行完毕,那么我们必须等待TASK,调用Task<TResult>的GetResultCore(waitCompletionNotification: true)方法,该方法最终调用Task的InternalWait方法。

/// 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.
    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;
    }
  /// 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.
   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;
    }
    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)
    }
    
   // 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;
    }
    private sealed class SetOnInvokeMres : ManualResetEventSlim, ITaskCompletionAction
    {
        internal SetOnInvokeMres() : base(false, 0) { }
        public void Invoke(Task completingTask) { Set(); }
    }

SpinThenBlockingWait方法先自旋检查是否完成,如果没有调用SetOnInvokeMres实例来完成Wait【SetOnInvokeMres是ManualResetEventSlim的子类,这里调用 mres.Wait(Timeout.Infinite, cancellationToken) 或者 mres.Wait((int)(millisecondsTimeout - elapsedTimeTicks), cancellationToken)来实现阻塞】,但是这里有一个AddCompletionAction方法,然后调用AddTaskContinuation方法,在AddTaskContinuation方法中有一句很重要【if ((m_continuationObject != null) || (Interlocked.CompareExchange(ref m_continuationObject, tc, null) != null))】说白了就是把SetOnInvokeMres实例赋给m_continuationObject对象。所以AddCompletionAction方法里面的action.Invoke(this);放法在这里没有调用。而是等待task执行完毕后FinishContinuations方法中的singleTaskCompletionAction.Invoke(this);设置信号量,如果m_continuationObject是真正的TaskContinuation实例,我们就调用【    singleTaskContinuation.Run(this, bCanInlineContinuations)】

我们简单总结一下一下调用次序:

TaskFactory.StartNew
->Task<TResult>.StartNew【该方法首先调用构造函数,构造函数里面调用PossiblyCaptureContext来捕获线程上下文,然后调用自己的ScheduleAndStart方法】
->TaskScheduler.InternalQueueTask
->ThreadPoolTaskScheduler.QueueTask【到这里Task就转换为线程了】
->Task.ExecuteEntry【如果是线程池是通过调用IThreadPoolWorkItem的ExecuteWorkItem方法进入的】
->Task.ExecuteWithThreadLocal【该方法首先拷贝捕获到的线程上下文,再调用ExecutionContext.Run,传入上传下文和回调方法】
->Task.ExecutionContextCallback
->Task.Execute
->Task<TResult>.InnerInvoke【该方法会真正调用我们自己的方法,并且给返回值赋值】
->Task.Finish 【标记该Task已经结束】
->Task.FinishStageTwo
->Task.FinishStageThree
->Task.FinishContinuations【会触发SET】
-------------------------
Task<TResult>的Result属性,就是Task的返回值,如果Task执行完毕直接返回,否者调用Task<TResult>.GetResultCore
->Task.InternalWait
->Task.SpinThenBlockingWait【调用SetOnInvokeMres的Wait方法】

posted on 2017-11-26 16:12  dz45693  阅读(21385)  评论(1编辑  收藏  举报

导航