.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é)。
- 如果 Task 是一個(gè) Top Task(從主線程或者其他線程創(chuàng)建的),ThreadPoolWorkQueueThreadLocals(ThreadStatic) 不會(huì)被初始化,Task 將
直接投遞到全局隊(duì)列中。 - 如果 TaskB 是在 TaskA 中創(chuàng)建的,TaskB 會(huì)被插入到 TaskA 當(dāng)前線程的工作隊(duì)列。因?yàn)樵?TaskA 的當(dāng)前線程中,ThreadPoolWorkQueueThreadLocals 已經(jīng)建立(后面會(huì)講到為什么)。
- 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。