{C#} How task works?

.net 從4.5開始對(duì) Task 有了良好的支持??梢院芊奖愕膭?chuàng)建任務(wù):

Task.Factory.StartNew(func<T>);

內(nèi)部邏輯類似于:

var task = new Task<T>(func, ...);
task.ScheduleAndStart(...);

那么當(dāng)我們寫下這行代碼時(shí),究竟發(fā)生了什么?
首先,任務(wù)在創(chuàng)建時(shí)可以指定任務(wù)調(diào)度器,如果不提供將采用默認(rèn)的線程池調(diào)度器(ThreadPoolTaskScheduler)。當(dāng) task 啟動(dòng)的時(shí)候,會(huì)把自身投遞到任務(wù)管理器中。

m_taskScheduler.InternalQueueTask(this);

internal void InternalQueueTask(Task task)
{
    Contract.Requires(task != null);

    task.FireTaskScheduledIfNeeded(this);

    this.QueueTask(task);
}

接著再看 ThreadPoolTaskScheduler.QueueTask(Task):

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

ThreadPoolTaskScheduler 其實(shí)是對(duì)線程池的封裝。不過,對(duì)于【暗示將要】長(zhǎng)時(shí)間運(yùn)行的任務(wù),為避免線程池阻塞,將直接創(chuàng)建新的線程。

ThreadPool.UnsafeQueueCustomWorkItem:

internal static void UnsafeQueueCustomWorkItem(IThreadPoolWorkItem workItem, bool forceGlobal)
{
    Contract.Assert(null != workItem);
    EnsureVMInitialized();

    //
    // Enqueue needs to be protected from ThreadAbort
    //
    try { }
    finally
    {
        ThreadPoolGlobals.workQueue.Enqueue(workItem, forceGlobal);
    }
}

很簡(jiǎn)單,就是將任務(wù)加入到任務(wù)隊(duì)列中。注意 Task,Task<T>,QueueUserWorkItemCallback 都實(shí)現(xiàn)了 IThreadPoolWorkItem 接口。

ThreadPoolGlobals.workQueue.Enqueue:

public void Enqueue(IThreadPoolWorkItem callback, bool forceGlobal)
{
    ThreadPoolWorkQueueThreadLocals tl = null;
    if (!forceGlobal)
        tl = ThreadPoolWorkQueueThreadLocals.threadLocals;

    if (loggingEnabled)
        System.Diagnostics.Tracing.FrameworkEventSource.Log.ThreadPoolEnqueueWorkObject(callback);
    
    if (null != tl)
    {
        tl.workStealingQueue.LocalPush(callback);
    }
    else
    {
        QueueSegment head = queueHead;

        while (!head.TryEnqueue(callback))
        {
            Interlocked.CompareExchange(ref head.Next, new QueueSegment(), null);

            while (head.Next != null)
            {
                Interlocked.CompareExchange(ref queueHead, head.Next, head);
                head = queueHead;
            }
        }
    }

    EnsureThreadRequested();
}

注意這段代碼里的細(xì)節(jié)。

  1. 如果 Task 是一個(gè) Top Task(從主線程或者其他線程創(chuàng)建的),ThreadPoolWorkQueueThreadLocals(ThreadStatic) 不會(huì)被初始化,Task 將
    直接投遞到全局隊(duì)列中。
  2. 如果 TaskB 是在 TaskA 中創(chuàng)建的,TaskB 會(huì)被插入到 TaskA 當(dāng)前線程的工作隊(duì)列。因?yàn)樵?TaskA 的當(dāng)前線程中,ThreadPoolWorkQueueThreadLocals 已經(jīng)建立(后面會(huì)講到為什么)。
  3. EnsureThreadRequested 會(huì)請(qǐng)求必要的線程(如果沒有超過最大可用線程)。具體創(chuàng)建線程的代碼不可見。

插入隊(duì)列的過程就到這里結(jié)束了。 可是,,我們的task什么時(shí)候能跑起來?
這就涉及到另一方面,線程是怎么從隊(duì)列里獲取任務(wù)的。線程大概會(huì)執(zhí)行ThreadPool.Dispatch,在這個(gè)方法中首先會(huì)創(chuàng)建自身的隊(duì)列,然后獲取任務(wù)并執(zhí)行。
獲取任務(wù)的算法比較有意思,這里貼出重要的部分。
ThreadPoolGlobals.workQueue.Dequeue(tl,...);

public void Dequeue(ThreadPoolWorkQueueThreadLocals tl, out IThreadPoolWorkItem callback, out bool missedSteal)
{
    callback = null;
    missedSteal = false;
    WorkStealingQueue wsq = tl.workStealingQueue;

    if (wsq.LocalPop(out callback))
        Contract.Assert(null != callback);

    if (null == callback)
    {
        QueueSegment tail = queueTail;
        while (true)
        {
            if (tail.TryDequeue(out callback))
            {
                Contract.Assert(null != callback);
                break;
            }

            if (null == tail.Next || !tail.IsUsedUp())
            {
                break;
            }
            else
            {
                Interlocked.CompareExchange(ref queueTail, tail.Next, tail);
                tail = queueTail;
            }
        }
    }

    if (null == callback)
    {
        WorkStealingQueue[] otherQueues = allThreadQueues.Current;
        int i = tl.random.Next(otherQueues.Length);
        int c = otherQueues.Length;
        while (c > 0)
        {
            WorkStealingQueue otherQueue = Volatile.Read(ref otherQueues[i % otherQueues.Length]);
            if (otherQueue != null &&
                otherQueue != wsq &&
                otherQueue.TrySteal(out callback, ref missedSteal))
            {
                Contract.Assert(null != callback);
                break;
            }
            i++;
            c--;
        }
    }
}

線程先從本身隊(duì)列里獲取任務(wù),然后從全局隊(duì)列獲取,最后從其他線程的隊(duì)列里偷任務(wù)。

值得注意的是,全局隊(duì)列是FIFO,本地隊(duì)列是LIFO。兩者都采用了輕量鎖機(jī)制,尤其是全局隊(duì)列,設(shè)計(jì)得非常巧妙。

接下來說說TaskA中創(chuàng)建TaskB的情況。TaskB在創(chuàng)建時(shí)如果沒有特別指定TaskScheduler,將使用TaskA的TaskScheduler。記住,是在TaskA運(yùn)行中的線程啟動(dòng)TaskB,而此線程在分發(fā)之時(shí)就已經(jīng)創(chuàng)建了本地隊(duì)列,根據(jù)ThreadPoolGlobals.workQueue.Enqueue算法,可以知道TaskB會(huì)被插入到本地隊(duì)列。所以,你應(yīng)該知道如何利用Task這一特性來有針對(duì)性的創(chuàng)建和啟動(dòng)task。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容