C# 溫故而知新: 線(xiàn)程篇(二)
線(xiàn)程池和異步線(xiàn)程
目錄:
在上一章中通過(guò)Thread對象創(chuàng )建我們所需要的線(xiàn)程,但是創(chuàng )建線(xiàn)程的開(kāi)銷(xiāo)是很大的,在需要以性能為重的項目中這的確容易導致一些性能問(wèn)題,
其實(shí)我們所想象中的線(xiàn)程開(kāi)銷(xiāo)最好如下表示:
1 盡量少的創(chuàng )建線(xiàn)程并且能將線(xiàn)程反復利用 2 最好不要銷(xiāo)毀而是掛起線(xiàn)程達到避免性能損失 3 通過(guò)一個(gè)技術(shù)達到讓?xiě)?span style="COLOR: rgb(255,0,0)">程序一個(gè)個(gè)執行工作,類(lèi)似于一個(gè)隊列 4 如果某一線(xiàn)程長(cháng)時(shí)間掛起而不工作的話(huà),需要徹底銷(xiāo)毀并且釋放資源 5 如果線(xiàn)程不夠用的話(huà)能夠創(chuàng )建線(xiàn)程,并且用戶(hù)可以自己定制最大線(xiàn)程創(chuàng )建的數量 |
令人欣慰的是微軟早就想到了以上幾點(diǎn),于是CLR線(xiàn)程池的概念出現了,說(shuō)到底線(xiàn)程池就是一個(gè)幫助我們開(kāi)發(fā)人員實(shí)現多線(xiàn)程的一個(gè)方案,就是
用來(lái)存放“線(xiàn)程”的對象池,利用線(xiàn)程池我們可以開(kāi)發(fā)出性能比較高的對于多線(xiàn)程的應用,同時(shí)減低一些不必要的性能損耗,我們不必去手動(dòng)創(chuàng )建
線(xiàn)程,線(xiàn)程池根據給定線(xiàn)程池中的任務(wù)隊列的隊列速度和相關(guān)任務(wù)執行速度相比較去自己添加或復用線(xiàn)程,關(guān)于線(xiàn)程池的細節我會(huì )在下文中詳細闡述
2 簡(jiǎn)單介紹下線(xiàn)程池各個(gè)優(yōu)點(diǎn)的實(shí)現細節
讓我們根據上節中線(xiàn)程池已經(jīng)實(shí)現了5個(gè)優(yōu)點(diǎn)來(lái)詳細介紹下線(xiàn)程池的功能
1 盡量少的創(chuàng )建線(xiàn)程并且能將線(xiàn)程反復利用
初始化的線(xiàn)程池中是沒(méi)有線(xiàn)程的,當應用程序區請求線(xiàn)程池時(shí),線(xiàn)程池會(huì )制造一個(gè)初始線(xiàn)程,一般情況下,線(xiàn)程池會(huì )重復使用這個(gè)線(xiàn)程來(lái)經(jīng)量少的創(chuàng )
建線(xiàn)程,這樣線(xiàn)程池就能盡量避免去創(chuàng )建新的線(xiàn)程而減少的創(chuàng )建線(xiàn)程的開(kāi)銷(xiāo)
2 最好不要銷(xiāo)毀而是掛起線(xiàn)程達到避免性能損失
當一個(gè)線(xiàn)程池中的線(xiàn)程工作完畢之后,該線(xiàn)程不會(huì )被銷(xiāo)毀而是被掛起操作等待,關(guān)于線(xiàn)程的掛起大家可以參考第一篇,如果應用程序又一次請求線(xiàn)程
池的話(huà),那么這個(gè)線(xiàn)程會(huì )重新被喚醒,從而是實(shí)現了線(xiàn)程的復用并且避免一定的性能損失
3 通過(guò)一個(gè)技術(shù)達到讓?xiě)贸绦蛞粋€(gè)個(gè)執行工作,類(lèi)似于一個(gè)隊列
多個(gè)應用程序請求線(xiàn)程池后,線(xiàn)程池會(huì )將各個(gè)應用程序排隊處理,首先利用線(xiàn)程池中的一個(gè)線(xiàn)程對各個(gè)應用程序進(jìn)行操作,如果應用程序的執行速度
超過(guò)了隊列的排隊速度時(shí),線(xiàn)程池會(huì )去創(chuàng )建一個(gè)新的線(xiàn)程,否則復用原來(lái)的線(xiàn)程
4 如果某一線(xiàn)程長(cháng)時(shí)間掛起而不工作的話(huà),需要徹底銷(xiāo)毀并且釋放資源
有可能在多個(gè)程序請求線(xiàn)程池執行后,線(xiàn)程池中產(chǎn)生了許多掛起的線(xiàn)程,并且這些線(xiàn)程池中的線(xiàn)程會(huì )一直處于空閑狀態(tài)間接導致的內存的浪費,所以微軟
為線(xiàn)程池設定了一個(gè)超時(shí)時(shí)間,當掛起的線(xiàn)程超時(shí)之后會(huì )自動(dòng)銷(xiāo)毀這些線(xiàn)程
5 如果線(xiàn)程不夠用的話(huà)能夠創(chuàng )建線(xiàn)程
前面已經(jīng)提到過(guò),有時(shí)候排在隊列中的其中一個(gè)或多個(gè)應用程序工作時(shí)間超過(guò)了規定的每個(gè)應用程序的排隊時(shí)間,那么線(xiàn)程池不會(huì )坐視不管,線(xiàn)程池會(huì )創(chuàng )建
一個(gè)新的線(xiàn)程來(lái)幫助另一個(gè)需要執行的應用程序
相信大家看完上述5個(gè)優(yōu)點(diǎn)及其細節后,對線(xiàn)程池的目的和優(yōu)點(diǎn)就豁然開(kāi)朗了
個(gè)人認為CLR線(xiàn)程池最牛的地方就是它能夠根據隊列中的應用程序執行時(shí)間和各個(gè)排隊應用程序間的 排隊速度進(jìn)行比較,從而決定是不是創(chuàng )建或者復用原先的線(xiàn)程,假如一系列的應用程序非常的簡(jiǎn)單 或者執行速度很快的情況下,根本無(wú)需創(chuàng )建新的線(xiàn)程,從而這個(gè)單一線(xiàn)程可以悠閑的掛起等待排隊 的下一個(gè)應用程序。如果應用程序非常復雜或者層次不齊,那么正好相反,由于這個(gè)線(xiàn)程正在忙, 所以無(wú)暇對排隊的下個(gè)任務(wù)進(jìn)行處理,所以需要創(chuàng )建一個(gè)新的線(xiàn)程處理,這樣陸陸續續會(huì )創(chuàng )建一些 新的線(xiàn)程來(lái)完成隊列中的應用程序,如果在執行過(guò)程中多余線(xiàn)程會(huì )超時(shí)自動(dòng)回收,而且CLR線(xiàn)程 池允許用戶(hù)自定義添加最大線(xiàn)程數和最小線(xiàn)程數,但是出于性能的考慮微軟不建議開(kāi)發(fā)人員手動(dòng)更 改線(xiàn)程池中的線(xiàn)程數量,對于以上幾點(diǎn)大家務(wù)必理解 |
如果您理解了線(xiàn)程池目的及優(yōu)點(diǎn)后,讓我們溫故下線(xiàn)程池的常用的幾個(gè)方法:
1. public static Boolean QueueUserWorkItem(WaitCallback wc, Object state);
WaitCallback回調函數就是前文所闡述的應用程序,通過(guò)將一些回調函數放入線(xiàn)程池中讓其形成隊列,然后線(xiàn)程池會(huì )自動(dòng)創(chuàng )建或者復用線(xiàn)程
去執行處理這些回調函數,
State: 這個(gè)參數也是非常重要的,當執行帶有參數的回調函數時(shí),該參數會(huì )將引用傳入,回調方法中,供其使用
3. public static bool SetMaxThreads(int workerThreads,int completionPortThreads);
4. public static bool SetMinThreads(int workerThreads,int completionPortThreads);
3和4方法 CLR線(xiàn)程池類(lèi)中預留的兩個(gè)能夠更改,線(xiàn)程池中的工作線(xiàn)程和I/O線(xiàn)程數量的方法。
使用該方法時(shí)有兩點(diǎn)必須注意:
1.不能將輔助線(xiàn)程的數目或 I/O 完成線(xiàn)程的數目設置為小于計算機的處理器數目。
2.微軟不建議程序員使用這兩個(gè)方法的原因是可能會(huì )影響到線(xiàn)程池中的性能
我們通過(guò)一個(gè)簡(jiǎn)單的例子來(lái)溫故下
using System;using System.Threading;namespace ThreadPoolApplication{ class Program { //設定任務(wù)數量 static int count = 5; static void Main(string[] args) { //關(guān)于ManualResetEvent大伙不必深究,后續章將會(huì )詳細闡述,這里由于假設 //讓線(xiàn)程池執行5個(gè)任務(wù)所以也為每個(gè)任務(wù)加上這個(gè)對象保持同步 ManualResetEvent[] events=new ManualResetEvent[count]; Console.WriteLine("當前主線(xiàn)程id:{0}",Thread.CurrentThread.ManagedThreadId); //循環(huán)每個(gè)任務(wù) for (int i = 0; i < count; i++) { //實(shí)例化同步工具 events[i]=new ManualResetEvent(false); //Test在這里就是任務(wù)類(lèi),將同步工具的引用傳入能保證共享區內每次只有一個(gè)線(xiàn)程進(jìn)入 Test tst = new Test(events[i]); Thread.Sleep(1000); //將任務(wù)放入線(xiàn)程池中,讓線(xiàn)程池中的線(xiàn)程執行該任務(wù)
ThreadPool.QueueUserWorkItem(tst.DisplayNumber, new { num1=2}); } //注意這里,設定WaitAll是為了阻塞調用線(xiàn)程(主線(xiàn)程),讓其余線(xiàn)程先執行完畢, //其中每個(gè)任務(wù)完成后調用其set()方法(收到信號),當所有 //的任務(wù)都收到信號后,執行完畢,將控制權再次交回調用線(xiàn)程(這里的主線(xiàn)程) ManualResetEvent.WaitAll(events); Console.ReadKey(); } } public class Test { ManualResetEvent manualEvent; public Test(ManualResetEvent manualEvent) { this.manualEvent = manualEvent; } public void DisplayNumber(object a) { Console.WriteLine("當前運算結果:{0}",((dynamic)a).num1); Console.WriteLine("當前子線(xiàn)程id:{0} 的狀態(tài):{1}", Thread.CurrentThread.ManagedThreadId,Thread.CurrentThread.ThreadState); //這里是方法執行時(shí)間的模擬,如果注釋該行代碼,就能看出線(xiàn)程池的功能了 //Thread.Sleep(30000); //這里是釋放共享鎖,讓其他線(xiàn)程進(jìn)入 manualEvent.Set(); } }}
執行結果:

從顯示結果能夠看出線(xiàn)程池只創(chuàng )建了id為9,10,11這3個(gè)線(xiàn)程來(lái)處理這5個(gè)任務(wù),因為每個(gè)任務(wù)的執行時(shí)間非常短,所以線(xiàn)程池
的優(yōu)勢被展現出來(lái)了
如果我們去掉DisplayNumber方法中的Thread.Sleep(30000) 的注釋的話(huà),會(huì )發(fā)現由于任務(wù)的執行時(shí)間遠遠超于任務(wù)在隊列中的
排隊時(shí)間,所以線(xiàn)程池開(kāi)啟了5個(gè)線(xiàn)程來(lái)執行任務(wù)

在很多時(shí)候例如UI或者IO操作時(shí)我們希望將這些很復雜且耗時(shí)比較長(cháng)的邏輯交給后臺線(xiàn)程去處理,而不想影響頁(yè)面的正常運行,而且
我們希望后臺線(xiàn)程能夠觸發(fā)一個(gè)回調事件來(lái)提示該任務(wù)已經(jīng)完成,所以基于這種需求越來(lái)越多而且在復雜的邏輯下也難以避免一些多線(xiàn)
程的死鎖,所以微軟為我們提供了一個(gè)屬于微軟自己的異步線(xiàn)程的概念,上一章提到了多線(xiàn)程和異步的基本概念和區別大家可以去溫故下,
| 線(xiàn)程異步指的是一個(gè)調用請求發(fā)送給被調用者,而調用者不用等待其結果的返回,一般異步執行的任務(wù)都需要比較長(cháng)的時(shí)間, |
相信大家理解的異步的概念后都能對異步的根源有個(gè)初步的認識,和線(xiàn)程一樣,異步也是針對執行方法而設計的,也就是說(shuō)當我們執行一個(gè)
方法時(shí),使用異步方式可以不阻礙主線(xiàn)程的運行而獨立運行,直到執行完畢后觸發(fā)回調事件,注意,.net異步線(xiàn)程也是通過(guò)內部線(xiàn)程池建立
的,雖然微軟將其封裝了起來(lái),但是我們也必須了解下
5 異步線(xiàn)程的工作過(guò)程和幾個(gè)重要的元素
由于委托是方法的抽象,那么如果委托上能設定異步調用的話(huà),方法也能實(shí)現異步,所以本節用異步委托來(lái)解釋下異步線(xiàn)程的工作過(guò)程
前文和前一章節中提到了多線(xiàn)程和異步的區別,對于異步線(xiàn)程來(lái)說(shuō),這正是體現了其工作方式:
調用者發(fā)送一個(gè)請求 -> 調用者去做自己的事情 -> 請求會(huì )異步執行 -> 執行完畢可以利用回調函數告訴調用者(也可以不用) |
在詳細說(shuō)明這幾個(gè)過(guò)程之前,讓我們來(lái)了解下下面的幾個(gè)重要的元素
AsyncCallback 委托
其實(shí)這個(gè)委托是微軟給我們提供的用于異步執行方法體后通知該異步方法已經(jīng)完成。AsyncCallBack抽象了所有異步方法執行后回調函數(方法)
,它規定了回調函數(方法)必須擁有一個(gè)IAsyncResult的參數并且沒(méi)有返回值,
IAsyncResult 接口
讓我們先來(lái)看下msdn上關(guān)于它的解釋
對于第一條的解釋,以下兩條代碼能夠直觀(guān)的理解:
有時(shí)候主線(xiàn)程需要等待異步執行后才能執行,雖然這違背的異步的初衷但是還是可以納入可能的需求行列,所以如果我們在beginInoke 后立刻使用EndInvoke的話(huà),主線(xiàn)程(調用者)會(huì )被阻塞,直到異步線(xiàn)程執行完畢后在啟動(dòng)執行 |
對于第二條的解釋:
結束異步操作時(shí)需要使用的回調方法,這里IAsyncResult作為參數被傳遞進(jìn)了個(gè)這方法,這時(shí)IAsyncResult起到了向回調方
法傳遞信息的作用,關(guān)于這點(diǎn)會(huì )在后文的異步線(xiàn)程的工作過(guò)程中詳細解釋下
我們最后再來(lái)看下IAsyncResult的幾個(gè)重要屬性

在這里再次強調下IAsyncResult第一個(gè)屬性AsyncState的作用,就像前面所說(shuō),有時(shí)我們需要將回調函數的參數傳入到回調方法體中,
當然傳入入口在BeginInvoke的第二個(gè)參數中,在回調函數體中我們可以通過(guò)將這個(gè)屬性類(lèi)型轉換成和BeginInvoke第二個(gè)參數一摸
一樣的類(lèi)型后加以使用
關(guān)于IAsyncResult最后還有一點(diǎn)補充:
| 如果IAsyncResult本身的功能還不能滿(mǎn)足你的需要的話(huà),可以自定義實(shí)現自己的AsyncResult類(lèi),但必須實(shí)現這個(gè)接口 |
理解了以上兩個(gè)關(guān)于異步至關(guān)重要的2個(gè)元素后,讓我們進(jìn)入一段段代碼,在來(lái)詳細看下異步線(xiàn)程的執行過(guò)程
//定義一個(gè)委托 public delegate void DoSomething(); static void Main(string[] args) { //1.實(shí)例化一個(gè)委托,調用者發(fā)送一個(gè)請求,請求執行該方法體(還未執行) DoSomething doSomething = new DoSomething( () => { Console.WriteLine("如果委托使用beginInvoke的話(huà),這里便是異步方法體"); //4,實(shí)現完這個(gè)方法體后自動(dòng)觸發(fā)下面的回調函數方法體 }); //3 。調用者(主線(xiàn)程)去觸發(fā)異步調用,采用異步的方式請求上面的方法體 IAsyncResult result= doSomething.BeginInvoke( //2.自定義上面方法體執行后的回調函數 new AsyncCallback ( //5.以下是回調函數方法體 //asyncResult.AsyncState其實(shí)就是AsyncCallback委托中的第二個(gè)參數 asyncResult => { doSomething.EndInvoke(asyncResult); Console.WriteLine(asyncResult.AsyncState.ToString()); } ) , "BeginInvoke方法的第二個(gè)參數就是傳入AsyncCallback中的AsyncResult.AsyncState,我們使用時(shí)可以強轉成相關(guān)類(lèi)型加以使用"); //DoSomething......調用者(主線(xiàn)程)會(huì )去做自己的事情 Console.ReadKey(); }
大家仔細看這面這段非常簡(jiǎn)單的代碼,為了大家理解方便我特意為異步執行過(guò)程加上了特有的注釋和序列號,這樣的話(huà),大伙能直觀(guān)初步的理解了異步的執行過(guò)程。
讓我們根據序列號來(lái)說(shuō)明下:
1. 實(shí)例化一個(gè)委托,調用者發(fā)送一個(gè)請求,請求執行該方法體(還未執行) 首先將委實(shí)例化并且定義好委托所請求的方法體,但是這個(gè)時(shí)候方法體是不會(huì )運行的 2. 這時(shí)候和第一步所相似的是,這里可以將定義好的回調函數AsyncCallback 方法體寫(xiě)入BeginInvoke的第一個(gè)參數,將需要傳入回調方法體的參數放入第二個(gè)參數 3.調用者(主線(xiàn)程)去觸發(fā)異步調用(執行BeginInvoke方法),采用異步的方式執行委托中的方法體 4.實(shí)現完這個(gè)方法體后自動(dòng)觸發(fā)下面的AsyncCallback中的方法體回調函數(可以設定回調函數為空來(lái)表示不需要回調) 5 . 執行回調函數方法體,注意使用委托的 EndInvoke方法結束異步操作,并且輸出顯示傳入異步回調函數的參數 再次強調第五點(diǎn): (1) 由于使用了回調函數,所以必然異步方法體已經(jīng)執行過(guò)了,所以在回調函數中使用EndInvoke方法是不會(huì )阻塞的, (2) 能通過(guò)EndInvoke方法獲得一些返回結果,例如FileStream.EndRead()能夠返回讀取的字節數等等 |
6 有必要簡(jiǎn)單介紹下Classic Async Pattern 和Event-based Async Pattern
首先介紹下Classic Async Pattern:
其實(shí)Classic Async Pattern指的就是我們常見(jiàn)的BeginXXX和EndXXX
IAsyncResult 異步設計模式通過(guò)名為 BeginOperationName 和 EndOperationName 的兩個(gè)方法來(lái)實(shí)現原同步方法的異步調用
讓我們再來(lái)回顧下.net中的幾個(gè)的BeginXXX 和EndXXX
Stream中的BeginRead,EndRead,BeginWrite,EndWrite Socket中的BeginReceive,EndReceive HttpWebRequest的BeginGetRequestStream和EndGetRequestStream.... |
再來(lái)介紹下Event-based Async Pattern
Event-based Async Pattern 值的是類(lèi)似于 xxxxxxxAsync() 和 類(lèi)似于event xxxxxCompleteHander
通過(guò)一個(gè)方法和一個(gè)完成事件來(lái)處理異步操作
.net中的例子:
WebClient.DownloadStringAsync(string uri)和 event DownloadStringCompleteEventHandler |
其實(shí)Classic Async Pattern和Event-based Async Pattern都是一種異步的設計思路,我們也可以根據這一系列的
思路去實(shí)現自己的異步方法
7 異步線(xiàn)程的發(fā)展趨勢以及.net4.5中異步的簡(jiǎn)化
微軟貌似現在把精力放在win8或WinPhone的metro上,而且記得在win 8開(kāi)發(fā)者培訓的會(huì )議上,著(zhù)重闡述了微軟對于異步的支持將越來(lái)越強,而且為了快
速響應諸如移動(dòng)設備的應用程序,微軟也在爭取為每個(gè)方法都實(shí)現一個(gè)異步版本…..可見(jiàn)異步的重要性,相信異步的發(fā)展趨勢是個(gè)不錯的
上升曲線(xiàn),還沒(méi)反應過(guò)來(lái).net4.5的異步新特性便誕生了。首先經(jīng)歷過(guò)異步摧殘的我們,都會(huì )有這樣一個(gè)感受,往往回調方法和普通方法
會(huì )搞錯,在復雜的項目面前,有時(shí)候簡(jiǎn)直無(wú)法維護,到處都是回調函數,眼花繚亂 所以微軟為了簡(jiǎn)化異步的實(shí)現過(guò)程,甚至大刀闊斧將
回調函數做成看起來(lái)像同步方法,雖然覺(jué)得很詭異,還是讓我們初步了解下這種異步的新特性
先看代碼
/// <summary> /// .net 4.5 中 async 和 await 全新的關(guān)鍵字 一起實(shí)現異步的簡(jiǎn)化 /// </summary> void async ShowUriContent(string uri) { using (FileStream fs = File.OpenRead("你的文件地址")) { using (FileStream fs2 = new FileStream()) { byte[] buffer = new byte[4096]; //FileStream的ReadAsync方法也是net4.5版本出現的,它返回一個(gè)Task<int>對象 //而且作用于await后的異步代碼會(huì )等待阻塞直到異步方法完成后返回 int fileBytesLength = await fs.ReadAsync(buffer,0,buffer.Length).ConfigureAwait(false); while(fileBytesLength>0) { //FileStream的WriteAsync方法也是net4.5版本出現的 await fs2.WriteAsync(buffer,0,buffer.Length).ConfigureAwait(false); } } } }
相信看完代碼后大家有耳目一新的感覺(jué),不錯,原本異步調用的回調函數不見(jiàn)了,取而代之的是await和方法聲明上的async關(guān)鍵字,新特性允許
我們實(shí)現這倆個(gè)關(guān)鍵字后便能在方法中實(shí)現“同步方式”的異步方法,其實(shí)這解決了一些棘手的問(wèn)題,諸如原本需要在回調事件里才能釋放的文件句
柄在這里和同步方法一樣,使用using便搞定了,還有截獲異常等等,都不用像之前那樣痛苦了,這里還有一些東東需要關(guān)注下,大家先不用去深
究ConfigureAwait這個(gè)方法,由于ReadAsync和 WriteAsync方法是.net 4.5新加的屬于返回Task<int>類(lèi)型的方法所以使用ConfigureAwait
方法能夠將數值取到,關(guān)于Task泛型類(lèi)我會(huì )在今后的章節中詳細闡述
自定義一個(gè)簡(jiǎn)單的線(xiàn)程池
static void Main(string[] args) { ThreadStart[] startArray = { new ThreadStart(()=>{ Console.WriteLine("第一個(gè)任務(wù)"); }), new ThreadStart(()=>{Console.WriteLine("第二個(gè)任務(wù)");}), new ThreadStart(()=>{Console.WriteLine("第三個(gè)任務(wù)");}), new ThreadStart(()=>{Console.WriteLine("第四個(gè)任務(wù)");}), };
MyThreadPool.SetMaxWorkThreadCount(2);
MyThreadPool.MyQueueUserWorkItem(startArray); Console.ReadKey(); } /// <summary> /// 自定義一個(gè)簡(jiǎn)單的線(xiàn)程池,該線(xiàn)程池實(shí)現了默認開(kāi)啟線(xiàn)程數 /// 當最大線(xiàn)程數全部在繁忙時(shí),循環(huán)等待,只到至少一個(gè)線(xiàn)程空閑為止 /// 本示例使用BackgroundWorker模擬后臺線(xiàn)程,任務(wù)將自動(dòng)進(jìn)入隊列和離開(kāi) /// 隊列 /// </summary> sealed class MyThreadPool { //線(xiàn)程鎖對象 private static object lockObj = new object(); //任務(wù)隊列 private static Queue<ThreadStart> threadStartQueue = new Queue<ThreadStart>(); //記錄當前工作的任務(wù)集合,從中可以判斷當前工作線(xiàn)程使用數,如果使用int判斷的話(huà)可能會(huì )有問(wèn)題, //用集合的話(huà)還能取得對象的引用,比較好 private static HashSet<ThreadStart> threadsWorker = new HashSet<ThreadStart>(); //當前允許最大工作線(xiàn)程數 private static int maxThreadWorkerCount = 1; //當前允許最小工作線(xiàn)程數 private static int minThreadWorkerCount = 0; /// <summary> /// 設定最大工作線(xiàn)程數 /// </summary> /// <param name="maxThreadCount">數量</param> public static void SetMaxWorkThreadCount(int maxThreadCount) { maxThreadWorkerCount =minThreadWorkerCount>maxThreadCount? minThreadWorkerCount : maxThreadCount; } /// <summary> /// 設定最小工作線(xiàn)程數 /// </summary> /// <param name="maxThreadCount">數量</param> public static void SetMinWorkThreadCount(int minThreadCount) { minThreadWorkerCount = minThreadCount > maxThreadWorkerCount ? maxThreadWorkerCount : minThreadCount; } /// <summary> /// 啟動(dòng)線(xiàn)程池工作 /// </summary> /// <param name="threadStartArray">任務(wù)數組</param> public static void MyQueueUserWorkItem(ThreadStart[] threadStartArray) { //將任務(wù)集合都放入到線(xiàn)程池中 AddAllThreadsToPool(threadStartArray); //線(xiàn)程池執行任務(wù) ExcuteTask(); } /// <summary> /// 將單一任務(wù)加入隊列中 /// </summary> /// <param name="ts">單一任務(wù)對象</param> private static void AddThreadToQueue(ThreadStart ts) { lock (lockObj) { threadStartQueue.Enqueue(ts); } } /// <summary> /// 將多個(gè)任務(wù)加入到線(xiàn)程池的任務(wù)隊列中 /// </summary> /// <param name="threadStartArray">多個(gè)任務(wù)</param> private static void AddAllThreadsToPool(ThreadStart[] threadStartArray) { foreach (var threadStart in threadStartArray) AddThreadToQueue(threadStart); } /// <summary> /// 執行任務(wù),判斷隊列中的任務(wù)數量是否大于0,如果是則判斷當前正在使用的工作線(xiàn)程的 /// 數量是否大于等于允許的最大工作線(xiàn)程數,如果一旦有線(xiàn)程空閑的話(huà) /// 就會(huì )執行ExcuteTaskInQueen方法處理任務(wù) /// </summary> private static void ExcuteTask() { while (threadStartQueue.Count > 0) { if (threadsWorker.Count < maxThreadWorkerCount) { ExcuteTaskInQueen(); } } } /// <summary> /// 執行出對列的任務(wù),加鎖保護 /// </summary> private static void ExcuteTaskInQueen() { lock (lockObj) { ExcuteTaskByThread(threadStartQueue.Dequeue()); } } /// <summary> /// 實(shí)現細節,這里使用BackGroudWork來(lái)實(shí)現后臺線(xiàn)程 /// 注冊doWork和Completed事件,當執行一個(gè)任務(wù)前,前將任務(wù)加入到 /// 工作任務(wù)集合(表示工作線(xiàn)程少了一個(gè)空閑),一旦RunWorkerCompleted事件被觸發(fā)則將任務(wù)從工作 /// 任務(wù)集合中移除(表示工作線(xiàn)程也空閑了一個(gè)) /// </summary> /// <param name="threadStart"></param> private static void ExcuteTaskByThread(ThreadStart threadStart) { threadsWorker.Add(threadStart); BackgroundWorker worker = new BackgroundWorker(); worker.DoWork += (o, e) => { threadStart.Invoke(); }; worker.RunWorkerCompleted += (o, e) => { threadsWorker.Remove(threadStart); }; worker.RunWorkerAsync(); } }
顯示結果:

有時(shí)我們需要使用IHttpAsyncHandler來(lái)異步實(shí)現一些特定的功能,讓我用很簡(jiǎn)單的示例來(lái)闡述這個(gè)過(guò)程
1:首先編寫(xiě)Handler1的邏輯,注意要繼承IHttpAsyncHandler接口
/// <summary> /// 異步IHttpHandler,實(shí)現了一個(gè)簡(jiǎn)單的統計流量的功能, /// 由于是示例代碼,所以沒(méi)有判斷IP或者M(jìn)AC /// </summary> public class Handler1 : IHttpAsyncHandler { //默認訪(fǎng)問(wèn)量是0 static int visitCount = 0; /// <summary> /// 這個(gè)HttpHandler的同步方法 /// </summary> /// <param name="context"></param> public void ProcessRequest(HttpContext context) { } public bool IsReusable { get { return false; } } /// <summary> /// 實(shí)現IHttpAsyncHandler 接口方法 /// </summary> /// <param name="context">當前HttpContext</param> /// <param name="cb">回調函數</param> /// <param name="extraData"></param> /// <returns></returns> public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData) { //這里可以加上判斷IP或mac的方法 visitCount++; //實(shí)例化AsyncUserVisiteCounterResult對象 AsyncUserVisiteCounterResult result = new AsyncUserVisiteCounterResult(cb, visitCount, context); result.Display(); return result; } /// <summary> /// 結束本次IHttpAsyncHandler工作時(shí)觸發(fā)的request方法 /// </summary> /// <param name="result"></param> public void EndProcessRequest(IAsyncResult result) { } } /// <summary> /// 自定義IAsyncResult 實(shí)現我們額外的Display方法 /// </summary> public class AsyncUserVisiteCounterResult : IAsyncResult { //回調參數 private object _param; //是否異步執行完成 private bool _asyncIsComplete; //回調方法 private AsyncCallback _callBack; //當前上下文 private HttpContext _context; public AsyncUserVisiteCounterResult(AsyncCallback callBack, object stateParam, HttpContext context) { this._callBack = callBack; this._param = stateParam; _asyncIsComplete = false; this._context = context; } public object AsyncState { get { return this._param; } } /// <summary> /// 等待句柄用于同步功能,關(guān)于等待句柄會(huì )在后續章節陳述/// </summary> public System.Threading.WaitHandle AsyncWaitHandle { get { return null; } } /// <summary> /// 該屬性表示不需要異步任務(wù)同步完成 /// </summary> public bool CompletedSynchronously { get { return false; } } /// <summary> /// 該屬性表示異步任務(wù)是否已經(jīng)執行完成 /// </summary> public bool IsCompleted { get { return this._asyncIsComplete; } } /// <summary> /// 自定義的額外功能,需要注意的是,執行完異步功能后 /// 要將_asyncIsComplete設置為true表示任務(wù)執行完畢而且 /// 執行回調方法,否則異步工作無(wú)法結束頁(yè)面會(huì )卡死 /// </summary> public void Display() { //這里先不用waitHandle句柄來(lái)實(shí)現同步 lock (this) { this._context.Response.Write("你是第" + (int)this._param + "位訪(fǎng)問(wèn)者,訪(fǎng)問(wèn)時(shí)間:"+DateTime.Now.ToString()); this._asyncIsComplete = true; this._callBack(this); } } }
2 在web.config中添加相應的配置,注意path指的是.ashx所在的路徑,指的是相應的文件類(lèi)型
<httpHandlers>
<add verb="*" path="AsyncThreadInAsp.net.Handler1.ashx" type="AsyncThreadInAsp.net.Handler1"/>
</httpHandlers>3 最后在頁(yè)面中訪(fǎng)問(wèn)

本章詳細介紹了CLR線(xiàn)程池和異步線(xiàn)程的一些概念和使用方法,包括線(xiàn)程池的優(yōu)點(diǎn)和細節,異步的執行過(guò)程和重要元素等等,在下一章節中,
我們開(kāi)始進(jìn)入到最為關(guān)鍵的線(xiàn)程同步環(huán)節,謝謝大家的鼓勵,文中有錯誤的地方可以隨時(shí)指出,也請大家多多支持!
聯(lián)系客服