{C#} How task works?
.net 從4.5開始對 Task 有了良好的支援。可以很方便的建立任務:
Task.Factory.StartNew(func<T>);
內部邏輯類似於:
var task = new Task<T>(func, ...);
task.ScheduleAndStart(...);
那麼當我們寫下這行程式碼時,究竟發生了什麼?
首先,任務在建立時可以指定任務排程器,如果不提供將採用預設的執行緒池排程器(ThreadPoolTaskScheduler)。當 task 啟動的時候,會把自身投遞到工作管理員中。
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 其實是對執行緒池的封裝。不過,對於【暗示將要】長時間執行的任務,為避免執行緒池阻塞,將直接建立新的執行緒。
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);
}
}
很簡單,就是將任務加入到任務佇列中。注意 Task,Task<T>,QueueUserWorkItemCallback 都實現了 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();
}
注意這段程式碼裡的細節。
- 如果 Task 是一個 Top Task(從主執行緒或者其他執行緒建立的),ThreadPoolWorkQueueThreadLocals(ThreadStatic) 不會被初始化,Task 將
直接投遞到全域性佇列中。 - 如果 TaskB 是在 TaskA 中建立的,TaskB 會被插入到 TaskA 當前執行緒的工作佇列。因為在 TaskA 的當前執行緒中,ThreadPoolWorkQueueThreadLocals 已經建立(後面會講到為什麼)。
- EnsureThreadRequested 會請求必要的執行緒(如果沒有超過最大可用執行緒)。具體建立執行緒的程式碼不可見。
插入佇列的過程就到這裡結束了。 可是,,我們的task什麼時候能跑起來?
這就涉及到另一方面,執行緒是怎麼從佇列裡獲取任務的。執行緒大概會執行ThreadPool.Dispatch,在這個方法中首先會建立自身的佇列,然後獲取任務並執行。
獲取任務的演算法比較有意思,這裡貼出重要的部分。
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--;
}
}
}
執行緒先從本身佇列裡獲取任務,然後從全域性佇列獲取,最後從其他執行緒的佇列裡偷任務。
值得注意的是,全域性佇列是FIFO,本地佇列是LIFO。兩者都採用了輕量鎖機制,尤其是全域性佇列,設計得非常巧妙。
接下來說說TaskA中建立TaskB的情況。TaskB在建立時如果沒有特別指定TaskScheduler,將使用TaskA的TaskScheduler。記住,是在TaskA執行中的執行緒啟動TaskB,而此執行緒在分發之時就已經建立了本地佇列,根據ThreadPoolGlobals.workQueue.Enqueue演算法,可以知道TaskB會被插入到本地佇列。所以,你應該知道如何利用Task這一特性來有針對性的建立和啟動task。
相關文章
- How Python list works?Python
- 2.3.3.3.1 How an Application Upgrade WorksAPP
- How React Works (一)首次渲染React
- how webpack Hot Module Replacement worksWeb
- how tomcat works(第18章:部署器)Tomcat
- C# Task若干問題淺析C#
- [How HTTPS works Part 1 — Building Blocks] HTTPS 的工作方式第 1 部分-基本構造塊HTTPUIBloC
- Ice works操作教程
- C# 非同步程式設計Task(三) async、awaitC#非同步程式設計AI
- Task03 && Task 04
- Camunda User Task:Task Listeners
- Valet 80 埠被佔用 It works
- How to ssh
- 走進Task(1):什麼是Task
- Event loop的macro task和micro taskOOPMac
- How to find dependency
- task 7
- Task01&Task02學習筆記筆記
- Task1&Task2學習筆記筆記
- .NET - Task.Run vs Task.Factory.StartNew
- How to Install psql on MacSQLMac
- How to Install LibreOffice on UbuntuUbuntu
- How OpenStack integrates with Ceph?
- How to Build a Cybersecurity CareerUI
- CISO之What & How
- [譯] WebAssembly: How and whyWeb
- How to Restart Qt ApplicationRESTQTAPP
- P.I. Works和Ooredoo斬獲大獎
- celery筆記三之task和task的呼叫筆記
- Task.Run(async () =>{}) 和 Task.Run(() =>{})區別
- Spark Task 的執行流程② - 建立、分發 TaskSpark
- task02
- [Javascript] Paralle TaskJavaScript
- task1
- joyful pandas task
- 聰明辦法學python task01&task02Python
- Task.Run(), Task.Factory.StartNew() 和 New Task() 的行為不一致分析
- C#關於在返回值為Task方法中使用Thread.Sleep引發的思考C#thread
- setTimeout(fn, 0) // it works - JavaScript 事件迴圈 動畫演示JavaScript事件動畫