天天看點

細說多線程(四)—— CLR線程池的工作者線程

目錄

<a target="_blank" href="http://blog.51cto.com/2689556/835785">一、線程的定義</a>

<a href="http://blog.51cto.com/2689556/835794" target="_blank">二、線程的基礎知識</a>

<a target="_blank" href="http://blog.51cto.com/2689556/835815">三、以ThreadStart方式實作多線程</a>

<a target="_blank" href="http://blog.51cto.com/2689556/835832">四、CLR線程池的工作者線程</a>

<a target="_blank" href="http://blog.51cto.com/2689556/835851">五、CLR線程池的I/O線程</a>

<a target="_blank" href="http://blog.51cto.com/2689556/835861">六、異步 SqlCommand</a>

<a target="_blank" href="http://blog.51cto.com/2689556/835866">七、并行程式設計與PLINQ</a>

<a href="http://79100812.blog.51cto.com/2689556/835881" target="_blank">八、計時器與鎖</a>

四、CLR線程池的工作者線程

4.1 關于CLR線程池

使用ThreadStart與ParameterizedThreadStart建立新線程非常簡單,但通過此方法建立的線程難于管理,若建立過多的線程反而會影響系統的性能。

有 見及此,.NET引入CLR線程池這個概念。CLR線程池并不會在CLR初始化的時候立刻建立線程,而是在應用程式要建立線程來執行任務時,線程池才初始 化一個線程。線程的初始化與其他的線程一樣。在完成任務以後,該線程不會自行銷毀,而是以挂起的狀态傳回到線程池。直到應用程式再次向線程池送出請求時, 線程池裡挂起的線程就會再度激活執行任務。這樣既節省了建立線程所造成的性能損耗,也可以讓多個任務反複重用同一線程,進而在應用程式生存期内節約大量開 銷。

注意:通過CLR線程池所建立的線程總是預設為背景線程,優先級數為ThreadPriority.Normal。

4.2 工作者線程與I/O線程

CLR線程池分為工作者線程(workerThreads)與I/O線程 (completionPortThreads) 兩種,工作者線程是主要用作管理CLR内部對象的運作,I/O(Input/Output) 線程顧名思義是用于與外部系統交換資訊,IO線程的細節将在下一節詳細說明。

通 過ThreadPool.GetMax(out int workerThreads,out int completionPortThreads )和 ThreadPool.SetMax( int workerThreads, int completionPortThreads)兩個方法可以分别讀取和設定CLR線程池中工作者線程與I/O線程的最大線程數。在 Framework2.0中最大線程預設為25*CPU數,在Framewok3.0、4.0中最大線程數預設為250*CPU數,在近年 I3,I5,I7 CPU出現後,線程池的最大值一般預設為1000、2000。

若想測試線程池中有多少的線程正在投入使用,可以通過ThreadPool.GetAvailableThreads( out int workerThreads,out int completionPortThreads ) 方法。

使用CLR線程池的工作者線程一般有兩種方式,一是直接通過 ThreadPool.QueueUserWorkItem() 方法,二是通過委托,下面将逐一細說。

4.3 通過QueueUserWorkItem啟動工作者線程

ThreadPool線程池中包含有兩個靜态方法可以直接啟動工作者線程:

一為 ThreadPool.QueueUserWorkItem(WaitCallback)

二為 ThreadPool.QueueUserWorkItem(WaitCallback,Object) 

先把WaitCallback委托指向一個帶有Object參數的無傳回值方法,再使用 ThreadPool.QueueUserWorkItem(WaitCallback) 就可以異步啟動此方法,此時異步方法的參數被視為null 。

運作結果

<a href="http://blog.51cto.com/attachment/201204/153014751.jpg" target="_blank"></a>

使用 ThreadPool.QueueUserWorkItem(WaitCallback,Object) 方法可以把object對象作為參數傳送到回調函數中。

下面例子中就是把一個string對象作為參數發送到回調函數當中。

<a href="http://blog.51cto.com/attachment/201204/153029647.jpg" target="_blank"></a>

通 過ThreadPool.QueueUserWorkItem啟動工作者線程雖然是友善,但WaitCallback委托指向的必須是一個帶有 Object參數的無傳回值方法,這無疑是一種限制。若方法需要有傳回值,或者帶有多個參數,這将多費周折。有見及此,.NET提供了另一種方式去建立工 作者線程,那就是委托。

4.4  委托類       

使用CLR線程池中的工作者線程,最靈活最常用的方式就是使用委托的異步方法,在此先簡單介紹一下委托類。

委托類包括以下幾個重要方法

<a href="http://blog.51cto.com/attachment/201204/153044270.jpg" target="_blank"></a>

當調用Invoke()方法時,對應此委托的所有方法都會被執行。而BeginInvoke與EndInvoke則支援委托方法的異步調用,由BeginInvoke啟動的線程都屬于CLR線程池中的工作者線程,在下面将詳細說明。

4.5  利用BeginInvoke與EndInvoke完成異步委托方法

首 先建立一個委托對象,通過IAsyncResult BeginInvoke(string name,AsyncCallback callback,object state) 異步調用委托方法,BeginInvoke 方法除最後的兩個參數外,其它參數都是與方法參數相對應的。通過 BeginInvoke 方法将傳回一個實作了 System.IAsyncResult 接口的對象,之後就可以利用EndInvoke(IAsyncResult ) 方法就可以結束異步操作,擷取委托的運作結果。

<a href="http://blog.51cto.com/attachment/201204/153103278.jpg" target="_blank"></a>

4.6  善用IAsyncResult

在 以上例子中可以看見,如果在使用myDelegate.BeginInvoke後立即調用myDelegate.EndInvoke,那在異步線程未完成 工作以前主線程将處于阻塞狀态,等到異步線程結束擷取計算結果後,主線程才能繼續工作,這明顯無法展示出多線程的優勢。此時可以好好利用 IAsyncResult 提高主線程的工作性能,IAsyncResult有以下成員:

通過輪詢方式,使用IsCompleted屬性判斷異步操作是否完成,這樣在異步操作未完成前就可以讓主線程執行另外的工作。

運作結果:

<a href="http://blog.51cto.com/attachment/201204/153118365.jpg" target="_blank"></a>

除 此以外,也可以使用WailHandle完成同樣的工作,WaitHandle裡面包含有一個方法WaitOne(int timeout),它可以判斷委托是否完成工作,在工作未完成前主線程可以繼續其他工作。運作下面代碼可得到與使用 IAsyncResult.IsCompleted 同樣的結果,而且更簡單友善 。

當要監視多個運作對象的時候,使用IAsyncResult.WaitHandle.WaitOne可就派不上用場了。

幸好.NET為WaitHandle準備了另外兩個靜态方法:WaitAny(waitHandle[], int)與WaitAll (waitHandle[] , int)。

其中WaitAll在等待所有waitHandle完成後再傳回一個bool值。

而WaitAny是等待其中一個waitHandle完成後就傳回一個int,這個int是代表已完成waitHandle在waitHandle[]中的數組索引。

下面就是使用WaitAll的例子,運作結果與使用 IAsyncResult.IsCompleted 相同。

4.7 回調函數

使 用輪詢方式來檢測異步方法的狀态非常麻煩,而且效率不高,有見及此,.NET為 IAsyncResult BeginInvoke(AsyncCallback , object)準備了一個回調函數。使用 AsyncCallback 就可以綁定一個方法作為回調函數,回調函數必須是帶參數 IAsyncResult 且無傳回值的方法: void AsycnCallbackMethod(IAsyncResult result) 。在BeginInvoke方法完成後,系統就會調用AsyncCallback所綁定的回調函數,最後回調函數中調用 XXX EndInvoke(IAsyncResult result) 就可以結束異步方法,它的傳回值類型與委托的傳回值一緻。

可以看到,主線在調用BeginInvoke方法可以繼續執行其他指令,而無需再等待了,這無疑比使用輪詢方式判斷異步方法是否完成更有優勢。

在異步方法執行完成後将會調用AsyncCallback所綁定的回調函數,注意一點,回調函數依然是在異步線程中執行,這樣就不會影響主線程的運作,這也使用回調函數最值得青昧的地方。

在回調函數中有一個既定的參數IAsyncResult,把IAsyncResult強制轉換為AsyncResult後,就可以通過 AsyncResult.AsyncDelegate 擷取原委托,再使用EndInvoke方法擷取計算結果。

運作結果如下:

<a href="http://blog.51cto.com/attachment/201204/153141866.jpg" target="_blank"></a>

如 果想為回調函數傳送一些外部資訊,就可以利用BeginInvoke(AsyncCallback,object)的最後一個參數object,它允許外 部向回調函數輸入任何類型的參數。隻需要在回調函數中利用 AsyncResult.AsyncState 就可以擷取object對象。

運作結果:

<a href="http://blog.51cto.com/attachment/201204/153156232.jpg" target="_blank"></a>

本文轉自 leslies2  51CTO部落格,原文連結:http://blog.51cto.com/79100812/835832