【转】1.2异步编程:使用线程池管理线程
???????? 從此圖中我們會(huì)發(fā)現(xiàn) .NET 與C# 的每個(gè)版本發(fā)布都是有一個(gè)“主題”。即:C#1.0托管代碼→C#2.0泛型→C#3.0LINQ→C#4.0動(dòng)態(tài)語言→C#5.0異步編程?,F(xiàn)在我為最新版本的“異步編程”主題寫系列分享,期待你的查看及點(diǎn)評(píng)。
?
?
傳送門:異步編程系列目錄……
?
?
開始《異步編程:使用線程池管理線程》
示例程序:異步編程:使用線程池管理線程.rar
??????? 如今的應(yīng)用程序越來越復(fù)雜,我們常常需要使用《異步編程:線程概述及使用》中提到的多線程技術(shù)來提高應(yīng)用程序的響應(yīng)速度。這時(shí)我們頻繁的創(chuàng)建和銷毀線程來讓應(yīng)用程序快速響應(yīng)操作,這頻繁的創(chuàng)建和銷毀無疑會(huì)降低應(yīng)用程序性能,我們可以引入緩存機(jī)制解決這個(gè)問題,此緩存機(jī)制需要解決如:緩存的大小問題、排隊(duì)執(zhí)行任務(wù)、調(diào)度空閑線程、按需創(chuàng)建新線程及銷毀多余空閑線程……如今微軟已經(jīng)為我們提供了現(xiàn)成的緩存機(jī)制:線程池
???????? 線程池原自于對(duì)象池,在詳細(xì)解說明線程池前讓我們先來了解下何為對(duì)象池。
?
對(duì)象池
在系統(tǒng)設(shè)計(jì)中,我們嘗嘗會(huì)使用到“池”的概念。Eg:數(shù)據(jù)庫(kù)連接池,socket連接池,線程池,組件隊(duì)列?!俺亍笨梢怨?jié)省對(duì)象重復(fù)創(chuàng)建和初始化所耗費(fèi)的時(shí)間。對(duì)那些被系統(tǒng)頻繁請(qǐng)求和使用的對(duì)象,使用此機(jī)制可以提高系統(tǒng)運(yùn)行性能。
“池”是一種“以空間換時(shí)間”的做法,我們?cè)趦?nèi)存中保存一系列整裝待命的對(duì)象,供人隨時(shí)差遣。與系統(tǒng)效率相比,這些對(duì)象所占用的內(nèi)存空間太微不足道了。
?
流程圖:
?
???????? 對(duì)于對(duì)象池的清理通常設(shè)計(jì)兩種方式:
1)???????? 手動(dòng)清理,即主動(dòng)調(diào)用清理的方法。
2)???????? 自動(dòng)清理,即通過System.Threading.Timer來實(shí)現(xiàn)定時(shí)清理。
?
關(guān)鍵實(shí)現(xiàn)代碼:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | public?sealed?class?ObjectPool<T> where?T : ICacheObjectProxy<T> { ????// 最大容量 ????private?Int32 m_maxPoolCount = 30; ????// 最小容量 ????private?Int32 m_minPoolCount = 5; ????// 已存容量 ????private?Int32 m_currentCount; ????// 空閑+被用 對(duì)象列表 ????private?Hashtable m_listObjects; ????// 最大空閑時(shí)間 ????private?int?maxIdleTime = 120; ????// 定時(shí)清理對(duì)象池對(duì)象 ????private?Timer timer = null; ? ????/// <summary> ????/// 創(chuàng)建對(duì)象池 ????/// </summary> ????/// <param name="maxPoolCount">最小容量</param> ????/// <param name="minPoolCount">最大容量</param> ????/// <param name="create_params">待創(chuàng)建的實(shí)際對(duì)象的參數(shù)</param> ????public?ObjectPool(Int32 maxPoolCount, Int32 minPoolCount, Object[] create_params){ } ? ????/// <summary> ????/// 獲取一個(gè)對(duì)象實(shí)例 ????/// </summary> ????/// <returns>返回內(nèi)部實(shí)際對(duì)象,若返回null則線程池已滿</returns> ????public?T GetOne(){ } ? ????/// <summary> ????/// 釋放該對(duì)象池 ????/// </summary> ????public?void?Dispose(){ } ? ????/// <summary> ????/// 將對(duì)象池中指定的對(duì)象重置并設(shè)置為空閑狀態(tài) ????/// </summary> ????public?void?ReturnOne(T obj){ } ? ????/// <summary> ????/// 手動(dòng)清理對(duì)象池 ????/// </summary> ????public?void?ManualReleaseObject(){ } ? ????/// <summary> ????/// 自動(dòng)清理對(duì)象池(對(duì)大于 最小容量 的空閑對(duì)象進(jìn)行釋放) ????/// </summary> ????private?void?AutoReleaseObject(Object obj){ } } |
?
???????? 通過對(duì)“對(duì)象池”的一個(gè)大體認(rèn)識(shí)能幫我們更快理解線程池。
?
線程池ThreadPool類詳解
ThreadPool靜態(tài)類,為應(yīng)用程序提供一個(gè)由系統(tǒng)管理的輔助線程池,從而使您可以集中精力于應(yīng)用程序任務(wù)而不是線程管理。每個(gè)進(jìn)程都有一個(gè)線程池,一個(gè)Process中只能有一個(gè)實(shí)例,它在各個(gè)應(yīng)用程序域(AppDomain)是共享的。
在內(nèi)部,線程池將自己的線程劃分工作者線程(輔助線程)和I/O線程。前者用于執(zhí)行普通的操作,后者專用于異步IO,比如文件和網(wǎng)絡(luò)請(qǐng)求,注意,分類并不說明兩種線程本身有差別,內(nèi)部依然是一樣的。
?
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | public?static?class?ThreadPool { ????// 將操作系統(tǒng)句柄綁定到System.Threading.ThreadPool。 ????public?static?bool?BindHandle(SafeHandle osHandle); ? ????// 檢索由ThreadPool.GetMaxThreads(Int32,Int32)方法返回的最大線程池線程數(shù)和當(dāng)前活動(dòng)線程數(shù)之間的差值。 ????public?static?void?GetAvailableThreads(out?int?workerThreads ????????????, out?int?completionPortThreads); ? ????// 設(shè)置和檢索可以同時(shí)處于活動(dòng)狀態(tài)的線程池請(qǐng)求的數(shù)目。 ????// 所有大于此數(shù)目的請(qǐng)求將保持排隊(duì)狀態(tài),直到線程池線程變?yōu)榭捎谩?/span> ????public?static?bool?SetMaxThreads(int?workerThreads, int?completionPortThreads); ????public?static?void?GetMaxThreads(out?int?workerThreads, out?int?completionPortThreads); ????// 設(shè)置和檢索線程池在新請(qǐng)求預(yù)測(cè)中維護(hù)的空閑線程數(shù)。 ????public?static?bool?SetMinThreads(int?workerThreads, int?completionPortThreads); ????public?static?void?GetMinThreads(out?int?workerThreads, out?int?completionPortThreads); ? ????// 將方法排入隊(duì)列以便執(zhí)行,并指定包含該方法所用數(shù)據(jù)的對(duì)象。此方法在有線程池線程變得可用時(shí)執(zhí)行。 ????public?static?bool?QueueUserWorkItem(WaitCallback callBack, object?state); ????// 將重疊的 I/O 操作排隊(duì)以便執(zhí)行。如果成功地將此操作排隊(duì)到 I/O 完成端口,則為 true;否則為 false。 ????// 參數(shù)overlapped:要排隊(duì)的System.Threading.NativeOverlapped結(jié)構(gòu)。 ????public?static?bool?UnsafeQueueNativeOverlapped(NativeOverlapped* overlapped); ????// 將指定的委托排隊(duì)到線程池,但不會(huì)將調(diào)用堆棧傳播到工作者線程。 ????public?static?bool?UnsafeQueueUserWorkItem(WaitCallback callBack, object?state); ? ????// 注冊(cè)一個(gè)等待Threading.WaitHandle的委托,并指定一個(gè) 32 位有符號(hào)整數(shù)來表示超時(shí)值(以毫秒為單位)。 ????// executeOnlyOnce如果為 true,表示在調(diào)用了委托后,線程將不再在waitObject參數(shù)上等待; ????// 如果為 false,表示每次完成等待操作后都重置計(jì)時(shí)器,直到注銷等待。 ????public?static?RegisteredWaitHandle RegisterWaitForSingleObject( ????????????WaitHandle waitObject ????????????, WaitOrTimerCallback callBack, object?state, ????????????Int millisecondsTimeOutInterval, bool?executeOnlyOnce); ????public?static?RegisteredWaitHandle UnsafeRegisterWaitForSingleObject( ??????????????WaitHandle waitObject ????????????, WaitOrTimerCallback callBack ????????????, object?state ????????????, int?millisecondsTimeOutInterval ????????????, bool?executeOnlyOnce); ????…… } |
1.???????? 線程池線程數(shù)
1)???????? 使用GetMaxThreads()和SetMaxThreads()獲取和設(shè)置最大線程數(shù)
可排隊(duì)到線程池的操作數(shù)僅受內(nèi)存的限制;而線程池限制進(jìn)程中可以同時(shí)處于活動(dòng)狀態(tài)的線程數(shù)(默認(rèn)情況下,限制每個(gè) CPU 可以使用 25 個(gè)工作者線程和 1,000 個(gè) I/O 線程(根據(jù)機(jī)器CPU個(gè)數(shù)和.net framework版本的不同,這些數(shù)據(jù)可能會(huì)有變化)),所有大于此數(shù)目的請(qǐng)求將保持排隊(duì)狀態(tài),直到線程池線程變?yōu)榭捎谩?/span>
不建議更改線程池中的最大線程數(shù):
a)???????? 將線程池大小設(shè)置得太大,可能會(huì)造成更頻繁的執(zhí)行上下文切換及加劇資源的爭(zhēng)用情況。
b)???????? 其實(shí)FileStream的異步讀寫,異步發(fā)送接受Web請(qǐng)求,System.Threading.Timer定時(shí)器,甚至使用delegate的beginInvoke都會(huì)默認(rèn)調(diào)用 ThreadPool,也就是說不僅你的代碼可能使用到線程池,框架內(nèi)部也可能使用到。
c)???????? 一個(gè)應(yīng)用程序池是一個(gè)獨(dú)立的進(jìn)程,擁有一個(gè)線程池,應(yīng)用程序池中可以有多個(gè)WebApplication,每個(gè)運(yùn)行在一個(gè)單獨(dú)的AppDomain中,這些WebApplication公用一個(gè)線程池。
?
2)???????? 使用GetMinThreads()和SetMinThreads()獲取和設(shè)置最小空閑線程數(shù)
為避免向線程分配不必要的堆??臻g,線程池按照一定的時(shí)間間隔創(chuàng)建新的空閑線程(該間隔為半秒)。所以如果最小空閑線程數(shù)設(shè)置的過小,在短期內(nèi)執(zhí)行大量任務(wù)會(huì)因?yàn)閯?chuàng)建新空閑線程的內(nèi)置延遲導(dǎo)致性能瓶頸。最小空閑線程數(shù)默認(rèn)值等于機(jī)器上的CPU核數(shù),并且不建議更改最小空閑線程數(shù)。
在啟動(dòng)線程池時(shí),線程池具有一個(gè)內(nèi)置延遲,用于啟用最小空閑線程數(shù),以提高應(yīng)用程序的吞吐量。
在線程池運(yùn)行中,對(duì)于執(zhí)行完任務(wù)的線程池線程,不會(huì)立即銷毀,而是返回到線程池,線程池會(huì)維護(hù)最小的空閑線程數(shù)(即使應(yīng)用程序所有線程都是空閑狀態(tài)),以便隊(duì)列任務(wù)可以立即啟動(dòng)。超過此最小數(shù)目的空閑線程一段時(shí)間沒事做后會(huì)自己醒來終止自己,以節(jié)省系統(tǒng)資源。
3)???????? 靜態(tài)方法GetAvailableThreads()
通過靜態(tài)方法GetAvailableThreads()返回的線程池線程的最大數(shù)目和當(dāng)前活動(dòng)數(shù)目之間的差值,即獲取線程池中當(dāng)前可用的線程數(shù)目
4)???????? 兩個(gè)參數(shù)
方法GetMaxThreads()、SetMaxThreads()、GetMinThreads()、SetMinThreads()、GetAvailableThreads()鈞包含兩個(gè)參數(shù)。參數(shù)workerThreads指工作者線程;參數(shù)completionPortThreads指異步 I/O 線程。
2.???????? 排隊(duì)工作項(xiàng)
通過調(diào)用 ThreadPool.QueueUserWorkItem 并傳遞 WaitCallback 委托來使用線程池。也可以通過使用 ThreadPool.RegisterWaitForSingleObject 并傳遞 WaitHandle(在向其發(fā)出信號(hào)或超時(shí)時(shí),它將引發(fā)對(duì)由 WaitOrTimerCallback 委托包裝的方法的調(diào)用)來將與等待操作相關(guān)的工作項(xiàng)排隊(duì)到線程池中。若要取消等待操作(即不再執(zhí)行WaitOrTimerCallback委托),可調(diào)用RegisterWaitForSingleObject()方法返回的RegisteredWaitHandle的 Unregister 方法。
如果您知道調(diào)用方的堆棧與在排隊(duì)任務(wù)執(zhí)行期間執(zhí)行的所有安全檢查不相關(guān),則還可以使用不安全的方法 ThreadPool.UnsafeQueueUserWorkItem 和 ThreadPool.UnsafeRegisterWaitForSingleObject。QueueUserWorkItem 和 RegisterWaitForSingleObject 都會(huì)捕獲調(diào)用方的堆棧,此堆棧將在線程池線程開始執(zhí)行任務(wù)時(shí)合并到線程池線程的堆棧中。如果需要進(jìn)行安全檢查,則必須檢查整個(gè)堆棧,但它還具有一定的性能開銷。使用“不安全的”方法調(diào)用并不會(huì)提供絕對(duì)的安全,但它會(huì)提供更好的性能。
3.?????????在一個(gè)內(nèi)核構(gòu)造可用時(shí)調(diào)用一個(gè)方法
讓一個(gè)線程不確定地等待一個(gè)內(nèi)核對(duì)象進(jìn)入可用狀態(tài),這對(duì)線程的內(nèi)存資源來說是一種浪費(fèi)。ThreadPool.RegisterWaitForSingleObject()為我們提供了一種方式:在一個(gè)內(nèi)核對(duì)象變得可用的時(shí)候調(diào)用一個(gè)方法。
使用需注意:
1)?????????WaitOrTimerCallback委托參數(shù),該委托接受一個(gè)名為timeOut的Boolean參數(shù)。如果?WaitHandle?在指定時(shí)間內(nèi)沒有收到信號(hào)(即,超時(shí)),則為?true,否則為?false。回調(diào)方法可以根據(jù)timeOut的值來針對(duì)性地采取措施。
2)?????????名為executeOnlyOnce的Boolean參數(shù)。傳true則表示線程池線程只執(zhí)行回調(diào)方法一次;若傳false則表示內(nèi)核對(duì)象每次收到信號(hào),線程池線程都會(huì)執(zhí)行回調(diào)方法。等待一個(gè)AutoResetEvent對(duì)象時(shí),這個(gè)功能尤其有用。
3)?????????RegisterWaitForSingleObject()方法返回一個(gè)RegisteredWaitHandle對(duì)象的引用。這個(gè)對(duì)象標(biāo)識(shí)了線程池正在它上面等待的內(nèi)核對(duì)象。我們可以調(diào)用它的Unregister(WaitHandle waitObject)方法取消由RegisterWaitForSingleObject()注冊(cè)的等待操作(即WaitOrTimerCallback委托不再執(zhí)行)。Unregister(WaitHandle waitObject)的WaitHandle參數(shù)表示成功取消注冊(cè)的等待操作后線程池會(huì)向此對(duì)象發(fā)出信號(hào)(set()),若不想收到此通知可以傳遞null。
?????????示例:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | private?static?void?Example_RegisterWaitForSingleObject() { ????// 加endWaitHandle的原因:如果執(zhí)行過快退出方法會(huì)導(dǎo)致一些東西被釋放,造成排隊(duì)的任務(wù)不能執(zhí)行,原因還在研究 ????AutoResetEvent endWaitHandle = new?AutoResetEvent(false); ? ????AutoResetEvent notificWaitHandle = new?AutoResetEvent(false); ????AutoResetEvent waitHandle = new?AutoResetEvent(false); ????RegisteredWaitHandle registeredWaitHandle = ThreadPool.RegisterWaitForSingleObject( ????????waitHandle, ????????(Object state, bool?timedOut) => ????????{ ????????????if?(timedOut) ????????????????Console.WriteLine("RegisterWaitForSingleObject因超時(shí)而執(zhí)行"); ????????????else ????????????????Console.WriteLine("RegisterWaitForSingleObject收到WaitHandle信號(hào)"); ????????}, ????????null, TimeSpan.FromSeconds(2), true ?????); ? ????// 取消等待操作(即不再執(zhí)行WaitOrTimerCallback委托) ????registeredWaitHandle.Unregister(notificWaitHandle); ? ????// 通知 ????ThreadPool.RegisterWaitForSingleObject( ????????notificWaitHandle, ????????(Object state, bool?timedOut) => ????????{ ????????????if?(timedOut) ????????????????Console.WriteLine("第一個(gè)RegisterWaitForSingleObject沒有調(diào)用Unregister()"); ????????????else ????????????????Console.WriteLine("第一個(gè)RegisterWaitForSingleObject調(diào)用了Unregister()"); ? ????????????endWaitHandle.Set(); ????????}, ????????null, TimeSpan.FromSeconds(4), true ?????); ? ????endWaitHandle.WaitOne(); } |
?
執(zhí)行上下文
???????? 上一小節(jié)中說到:線程池最大線程數(shù)設(shè)置過大可能會(huì)造成Windows頻繁執(zhí)行上下文切換,降低程序性能。對(duì)于大多數(shù)園友不會(huì)滿意這樣的回答,我和你一樣也喜歡“知其然,再知其所以然”。
1.???????? 上下文切換中的“上下文”是什么?
.NET中上下文太多,我最后得出的結(jié)論是:上下文切換中的上下文專指“執(zhí)行上下文”。
執(zhí)行上下文包括:安全上下文、同步上下文(System.Threading.SynchronizationContext)、邏輯調(diào)用上下文(System.Runtime.Messaging.CallContext)。即:安全設(shè)置(壓縮棧、Thread的Principal屬性和Windows身份)、宿主設(shè)置(System.Threading.HostExcecutingContextManager)以及邏輯調(diào)用上下文數(shù)據(jù)(System.Runtime.Messaging.CallContext的LogicalSetData()和LogicalGetData()方法)。
2.???????? 何時(shí)執(zhí)行“上下文切換”?
當(dāng)一個(gè)“時(shí)間片”結(jié)束時(shí),如果Windows決定再次調(diào)度同一個(gè)線程,那么Windows不會(huì)執(zhí)行上下文切換。如果Windows調(diào)度了一個(gè)不同的線程,這時(shí)Windows執(zhí)行線程上下文切換。
3.???????? “上下文切換”造成的性能影響
???????? 當(dāng)Windows上下文切換到另一個(gè)線程時(shí),CPU將執(zhí)行一個(gè)不同的線程,而之前線程的代碼和數(shù)據(jù)還在CPU的高速緩存中,(高速緩存使CPU不必經(jīng)常訪問RAM,RAM的速度比CPU高速緩存慢得多),當(dāng)Windows上下文切換到一個(gè)新線程時(shí),這個(gè)新線程極有可能要執(zhí)行不同的代碼并訪問不同的數(shù)據(jù),這些代碼和數(shù)據(jù)不在CPU的高速緩存中。因此,CPU必須訪問RAM來填充它的高速緩存,以恢復(fù)高速執(zhí)行狀態(tài)。但是,在其“時(shí)間片”執(zhí)行完后,一次新的線程上下文切換又發(fā)生了。
上下文切換所產(chǎn)生的開銷不會(huì)換來任何內(nèi)存和性能上的收益。執(zhí)行上下文所需的時(shí)間取決于CPU架構(gòu)和速度(即“時(shí)間片”的分配)。而填充CPU緩存所需的時(shí)間取決于系統(tǒng)運(yùn)行的應(yīng)用程序、CPU、緩存的大小以及其他各種因素。所以,無法為每一次線程上下文切換的時(shí)間開銷給出一個(gè)確定的值,甚至無法給出一個(gè)估計(jì)的值。唯一確定的是,如果要構(gòu)建高性能的應(yīng)用程序和組件,就應(yīng)該盡可能避免線程上下文切換。
除此之外,執(zhí)行垃圾回收時(shí),CLR必須掛起(暫停)所有線程,遍歷它們的棧來查找根以便對(duì)堆中的對(duì)象進(jìn)行標(biāo)記,再次遍歷它們的棧(有的對(duì)象在壓縮期間發(fā)生了移動(dòng),所以要更新它們的根),再恢復(fù)所有線程。所以,減少線程的數(shù)量也會(huì)顯著提升垃圾回收器的性能。每次使用一個(gè)調(diào)試器并遇到一個(gè)斷點(diǎn),Windows都會(huì)掛起正在調(diào)試的應(yīng)用程序中的所有線程,并在單步執(zhí)行或運(yùn)行應(yīng)用程序時(shí)恢復(fù)所有線程。因此,你用的線程越多,調(diào)試體驗(yàn)也就越差。
4.???????? 監(jiān)視Windows上下文切換工具
Windows實(shí)際記錄了每個(gè)線程被上下文切換到的次數(shù)??梢允褂孟馦icrosoft Spy++這樣的工具查看這個(gè)數(shù)據(jù)。這個(gè)工具是Visual Studio附帶的一個(gè)小工具(vs按安裝路徑\Visual Studio 2012\Common7\Tools),如圖
5.???????? 執(zhí)行上下文類詳解
在《異步編程:線程概述及使用》中我提到了Thread的兩個(gè)上下文,即:
1)???????? CurrentContext??????? 獲取線程正在其中執(zhí)行的當(dāng)前上下文。主要用于線程內(nèi)部存儲(chǔ)數(shù)據(jù)。
2)???????? ExecutionContext??? 獲取一個(gè)System.Threading.ExecutionContext對(duì)象,該對(duì)象包含有關(guān)當(dāng)前線程的各種上下文的信息。主要用于線程間數(shù)據(jù)共享。
其中獲取到的System.Threading.ExecutionContext就是本小節(jié)要說的“執(zhí)行上下文”。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public?sealed?class?ExecutionContext : IDisposable, ISerializable { ????public?void?Dispose(); ????public?void?GetObjectData(SerializationInfo info, StreamingContext context); ? ????// 此方法對(duì)于將執(zhí)行上下文從一個(gè)線程傳播到另一個(gè)線程非常有用。 ????public?ExecutionContext CreateCopy(); ????// 從當(dāng)前線程捕獲執(zhí)行上下文的一個(gè)副本。 ????public?static?ExecutionContext Capture(); ????// 在當(dāng)前線程上的指定執(zhí)行上下文中運(yùn)行某個(gè)方法。 ????public?static?void?Run(ExecutionContext executionContext, ContextCallback callback, object?state); ? ????// 取消執(zhí)行上下文在異步線程之間的流動(dòng)。 ????public?static?AsyncFlowControl SuppressFlow(); ????public?static?bool?IsFlowSuppressed(); ????// RestoreFlow? 撤消以前的 SuppressFlow 方法調(diào)用的影響。 ????// 此方法由 SuppressFlow 方法返回的 AsyncFlowControl 結(jié)構(gòu)的 Undo 方法調(diào)用。 ????// 應(yīng)使用 Undo 方法(而不是 RestoreFlow 方法)恢復(fù)執(zhí)行上下文的流動(dòng)。 ????public?static?void?RestoreFlow(); } |
ExecutionContext 類提供的功能讓用戶代碼可以在用戶定義的異步點(diǎn)之間捕獲和傳輸此上下文。公共語言運(yùn)行時(shí)(CLR)確保在托管進(jìn)程內(nèi)運(yùn)行時(shí)定義的異步點(diǎn)之間一致地傳輸 ExecutionContext。
每當(dāng)一個(gè)線程(初始線程)使用另一個(gè)線程(輔助線程)執(zhí)行任務(wù)時(shí),CLR會(huì)將前者的執(zhí)行上下文流向(復(fù)制到)輔助線程(注意這個(gè)自動(dòng)流向是單方向的)。這就確保了輔助線程執(zhí)行的任何操作使用的是相同的安全設(shè)置和宿主設(shè)置。還確保了初始線程的邏輯調(diào)用上下文可以在輔助線程中使用。
但執(zhí)行上下文的復(fù)制會(huì)造成一定的性能影響。因?yàn)閳?zhí)行上下文中包含大量信息,而收集所有這些信息,再把它們復(fù)制到輔助線程,要耗費(fèi)不少時(shí)間。如果輔助線程又采用了更多地輔助線程,還必須創(chuàng)建和初始化更多的執(zhí)行上下文數(shù)據(jù)結(jié)構(gòu)。
所以,為了提升應(yīng)用程序性能,我們可以阻止執(zhí)行上下文的流動(dòng)。當(dāng)然這只有在輔助線程不需要或者不訪問上下文信息的時(shí)候才能進(jìn)行阻止。
下面給出一個(gè)示例為了演示:
1)???????? 在線程間共享邏輯調(diào)用上下文數(shù)據(jù)(CallContext)。
2)???????? 為了提升性能,阻止\恢復(fù)執(zhí)行上下文的流動(dòng)。
3)???????? 在當(dāng)前線程上的指定執(zhí)行上下文中運(yùn)行某個(gè)方法。
?
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | private?static?void?Example_ExecutionContext() { ????CallContext.LogicalSetData("Name", "小紅"); ????Console.WriteLine("主線程中Name為:{0}", CallContext.LogicalGetData("Name")); ? ????// 1)?? 在線程間共享邏輯調(diào)用上下文數(shù)據(jù)(CallContext)。 ????Console.WriteLine("1)在線程間共享邏輯調(diào)用上下文數(shù)據(jù)(CallContext)。"); ????ThreadPool.QueueUserWorkItem((Object obj) ????????=> Console.WriteLine("ThreadPool線程中Name為:\"{0}\"", CallContext.LogicalGetData("Name"))); ????Thread.Sleep(500); ????Console.WriteLine(); ????// 2)?? 為了提升性能,取消\恢復(fù)執(zhí)行上下文的流動(dòng)。 ????ThreadPool.UnsafeQueueUserWorkItem((Object obj) ????????=> Console.WriteLine("ThreadPool線程使用Unsafe異步執(zhí)行方法來取消執(zhí)行上下文的流動(dòng)。Name為:\"{0}\"" ????????, CallContext.LogicalGetData("Name")), null); ????Console.WriteLine("2)為了提升性能,取消/恢復(fù)執(zhí)行上下文的流動(dòng)。"); ????AsyncFlowControl flowControl = ExecutionContext.SuppressFlow(); ????ThreadPool.QueueUserWorkItem((Object obj) ????????=> Console.WriteLine("(取消ExecutionContext流動(dòng))ThreadPool線程中Name為:\"{0}\"", CallContext.LogicalGetData("Name"))); ????Thread.Sleep(500); ????// 恢復(fù)不推薦使用ExecutionContext.RestoreFlow() ????flowControl.Undo(); ????ThreadPool.QueueUserWorkItem((Object obj) ????????=> Console.WriteLine("(恢復(fù)ExecutionContext流動(dòng))ThreadPool線程中Name為:\"{0}\"", CallContext.LogicalGetData("Name"))); ????Thread.Sleep(500); ????Console.WriteLine(); ????// 3)?? 在當(dāng)前線程上的指定執(zhí)行上下文中運(yùn)行某個(gè)方法。(通過獲取調(diào)用上下文數(shù)據(jù)驗(yàn)證) ????Console.WriteLine("3)在當(dāng)前線程上的指定執(zhí)行上下文中運(yùn)行某個(gè)方法。(通過獲取調(diào)用上下文數(shù)據(jù)驗(yàn)證)"); ????ExecutionContext curExecutionContext = ExecutionContext.Capture(); ????ExecutionContext.SuppressFlow(); ????ThreadPool.QueueUserWorkItem( ????????(Object obj) => ????????{ ????????????ExecutionContext innerExecutionContext = obj as?ExecutionContext; ????????????ExecutionContext.Run(innerExecutionContext, (Object state) ????????????????=> Console.WriteLine("ThreadPool線程中Name為:\"{0}\""<br>?????????????????????? , CallContext.LogicalGetData("Name")), null); ????????} ????????, curExecutionContext ?????); } |
結(jié)果如圖:
???????? 注意:
1)???????? 示例中“在當(dāng)前線程上的指定執(zhí)行上下文中運(yùn)行某個(gè)方法”:代碼中必須使用ExecutionContext.Capture()獲取當(dāng)前執(zhí)行上下文的一個(gè)副本。
a)???????? 若直接使用Thread.CurrentThread.ExecutionContext則會(huì)報(bào)“無法應(yīng)用以下上下文: 跨 AppDomains 封送的上下文、不是通過捕獲操作獲取的上下文或已作為 Set 調(diào)用的參數(shù)的上下文?!卞e(cuò)誤。
b)???????? 若使用Thread.CurrentThread.ExecutionContext.CreateCopy()會(huì)報(bào)“只能復(fù)制新近捕獲(ExecutionContext.Capture())的上下文”。
2)???????? 取消執(zhí)行上下文流動(dòng)除了使用ExecutionContext.SuppressFlow()方式外。還可以通過使用ThreadPool的UnsafeQueueUserWorkItem 和 UnsafeRegisterWaitForSingleObject來執(zhí)行委托方法。原因是不安全的線程池操作不會(huì)傳輸壓縮堆棧。每當(dāng)壓縮堆棧流動(dòng)時(shí),托管的主體、同步、區(qū)域設(shè)置和用戶上下文也隨之流動(dòng)。
?
線程池線程中的異常
線程池線程中未處理的異常將終止進(jìn)程。以下為此規(guī)則的三種例外情況:
1. 由于調(diào)用了 Abort,線程池線程中將引發(fā)ThreadAbortException。
2. 由于正在卸載應(yīng)用程序域,線程池線程中將引發(fā)AppDomainUnloadedException。
3. 公共語言運(yùn)行庫(kù)或宿主進(jìn)程將終止線程。
何時(shí)不使用線程池線程
現(xiàn)在大家都已經(jīng)知道線程池為我們提供了方便的異步API及托管的線程管理。那么是不是任何時(shí)候都應(yīng)該使用線程池線程呢?當(dāng)然不是,我們還是需要“因地制宜”的,在以下幾種情況下,適合于創(chuàng)建并管理自己的線程而不是使用線程池線程:
1.???????? 需要前臺(tái)線程。(線程池線程“始終”是后臺(tái)線程)
2.???????? 需要使線程具有特定的優(yōu)先級(jí)。(線程池線程都是默認(rèn)優(yōu)先級(jí),“不建議”進(jìn)行修改)
3.???????? 任務(wù)會(huì)長(zhǎng)時(shí)間占用線程。由于線程池具有最大線程數(shù)限制,因此大量占用線程池線程可能會(huì)阻止任務(wù)啟動(dòng)。
4.???????? 需要將線程放入單線程單元(STA)。(所有ThreadPool線程“始終”是多線程單元(MTA)中)
5.???????? 需要具有與線程關(guān)聯(lián)的穩(wěn)定標(biāo)識(shí),或使某一線程專用于某一任務(wù)。
?
?
本博文介紹線程池以及其基礎(chǔ)對(duì)象池,ThreadPool類的使用及注意事項(xiàng),如何排隊(duì)工作項(xiàng)到線程池,執(zhí)行上下文及線程上下文傳遞問題……?
線程池雖然為我們提供了異步操作的便利,但是它不支持對(duì)線程池中單個(gè)線程的復(fù)雜控制致使我們有些情況下會(huì)直接使用Thread。并且它對(duì)“等待”操作、“取消”操作、“延續(xù)”任務(wù)等操作比較繁瑣,可能迫使你從新造輪子。微軟也想到了,所以在.NET4.0的時(shí)候加入了“并行任務(wù)”并在.NET4.5中對(duì)其進(jìn)行改進(jìn),想了解“并行任務(wù)”的園友可以先看看《(譯)關(guān)于Async與Await的FAQ》。
本節(jié)到此結(jié)束,感謝大家的觀賞。贊的話還請(qǐng)多推薦啊 (*^_^*)
?
?
?
?
參考資料:《CLR via C#(第三版)》
總結(jié)
以上是生活随笔為你收集整理的【转】1.2异步编程:使用线程池管理线程的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 腾讯良心软件QQ影音下线 推荐恒星播放器
- 下一篇: 顶配卖18999元!苹果M2版MacBo