c#多執行緒:執行緒池和非同步程式設計
System.Threading.ThreadPool 類
System.Threading.Timer 類
如果執行緒的數目並不是很多,而且你想控制每個執行緒的細節諸如執行緒的優先順序等,使用Thread是比較合適的;但是如果有大量的執行緒,考慮使用執行緒池應該更好一些,它提供了高效的執行緒管理機制來處理多工。 對於定期的執行任務Timer類是合適的;使用代表是非同步方法呼叫的首選。
System.Threading.ThreadPool Class
當你建立應用程式時,你應該認識到大部分時間你的執行緒在空閒的等待某些事件的發生(諸如按下一個鍵或偵聽套節子的請求)。毫無疑問的,你也會認為這是絕對的浪費資源。
如果這裡有很多的任務需要完成,每個任務需要一個執行緒,你應該考慮使用執行緒池來更有效的管理你的資源並且從中受益。執行緒池是執行的多個執行緒集合,它允許你新增以執行緒自動建立和開始的任務到佇列裡面去。使用執行緒池使得你的系統可以優化執行緒在CPU使用時的時間碎片。但是要記住在任何特定的時間點,每一個程序和每個執行緒池只有一個一個正在執行的執行緒。這個類使得你的執行緒組成的池可以被系統管理,而使你的主要精力集中在工作流的邏輯而不是執行緒的管理。
當第一次例項化ThreadPool類時執行緒池將被建立。它有一個預設的上限,即每處理器最多可以有25個,但是這個上限是可以改變的。這樣使得處理器不會閒置下來。如果其中一個執行緒等待某個事件的發生,執行緒池將初始化另外一個執行緒並投入處理器工作,執行緒池就是這樣不停的建立工作的執行緒和分配任務給那些沒有工作的在佇列裡的執行緒。唯一的限制是工作執行緒的數目不能超過最大允許的數目。每個執行緒將執行在預設的優先順序和使用預設的屬於多執行緒空間的堆疊大小空間。一旦一項工作任務被加入佇列,你是不能取消的。
請求執行緒池處理一個任務或者工作項可以呼叫QueueUserWorkItem方法。這個方法帶一個WaitCallback代表型別的引數,這個引數包裝了你藥完成的任務。執行時自動為每一個的任務建立執行緒並且在任務釋放時釋放執行緒。
下面的程式碼說明了如何建立執行緒池和怎樣新增任務:
public void afunction(object o)
{
// do what ever the function is supposed to do.
}
//thread entry code
{
// create an instance of WaitCallback
WaitCallback myCallback = new WaitCallback (afunction);
//add this to the thread pool / queue a task
ThreadPool.QueueUserWorkItem (myCallback);
}
你也可以通過呼叫ThreadPool.RegisterWaitForSingleObject方法來傳遞一個System.Threading.WaitHandle,當被通知或者時間超過了呼叫被System.Threading.WaitOrTimerCallback包裝的方法。
執行緒池和基於事件的程式設計模式使得執行緒池對註冊的WaitHandles的監控和對合適的WaitOrTimerCallback代表方法的呼叫十分簡單(當WaitHandle被釋放時)。這些做法其實很簡單。這裡有一個執行緒不斷的觀測線上程池佇列等待操作的狀態。一旦等待操作完成,一個執行緒將被執行與其對應的任務。因此,這個方法隨著出發觸發事件的發生而增加一個執行緒。
讓我們看看怎麼隨事件新增一個執行緒到執行緒池,其實很簡單。我們只需要建立一個ManualResetEvent類的事件和一個WaitOrTimerCallback的代表,然後我們需要一個攜帶代表狀態的物件,同時我們也要決定休息間隔和執行方式。我們將上面的都新增到執行緒池,並且激發一個事件:
public void afunction(object o)
{
// do what ever the function is supposed to do.
}
//object that will carry the status information
public class anObject
{
}
//thread entry code
{
//create an event object
ManualResetEvent aevent = new ManualResetEvent (false);
// create an instance of WaitOrTimerCallback
WaitOrTimerCallback thread_method = new WaitOrTimerCallback (afunction);
// create an instance of anObject
anObject myobj = new anObject();
// decide how thread will perform
int timeout_interval = 100; // timeout in milli-seconds.
bool onetime_exec = true;
//add all this to the thread pool.
ThreadPool. RegisterWaitForSingleObject (aevent, thread_method, myobj, timeout_interval, onetime_exec);
// raise the event
aevent.Set();
}
在QueueUserWorkItem和RegisterWaitForSingleObject方法中,執行緒池建立了一個後臺的執行緒來回調。當執行緒池開始執行一個任務,兩個方法都將呼叫者的堆疊合併到執行緒池的執行緒堆疊中。如果需要安全檢查將耗費更多的時間和增加系統的負擔,因此可以通過使用它們對應的不安全的方法來避免安全檢查。就是ThreadPool.UnsafeRegisterWaitForSingleObject 和ThreadPool.UnsafeQueueUserWorkItem。
你也可以對與等待操作無關的任務排隊。 Timer-queue timers and registered wait operations也使用執行緒池。它們的返回方法也被放入執行緒池排隊。
執行緒池是非常有用的,被廣泛的用於。NET平臺上的套節子程式設計,等待操作註冊,程序計時器和非同步的I/O。對於小而短的任務,執行緒池提供的機制也是十分便利處於多執行緒的。執行緒池對於完成許多獨立的任務而且不需要逐個的設定執行緒屬性是十分便利的。但是,你也應該很清楚,有很多的情況是可以用其他的方法來替代執行緒池的。比如說你的計劃任務或給每個執行緒特定的屬性,或者你需要將執行緒放入單個執行緒的空間(而執行緒池是將所有的執行緒放入一個多執行緒空間),抑或是一個特定的任務是很冗長的,這些情況你最好考慮清楚,安全的辦法比用執行緒池應該是你的選擇。
System.Threading.Timer Class
Timer類對於週期性的在分離的執行緒執行任務是非常有效的,它不能被繼承。
這個類尤其用來開發控制檯應用程式,因為System.Windows.Forms.Time是不可用的。比如同來備份檔案和檢查資料庫的一致性。
當建立Timer物件時,你藥估計在第一個代理呼叫之前等待的時間和後來的每次成功呼叫之間的時間。一個定時呼叫發生在方法的應得時間過去,並且在後來週期性的呼叫這個方法。你可以適應Timer的Change方法來改變這些設定的值或者使Timer失效。當定時器Timer不再使用時,你應該呼叫Dispose方法來釋放其資源。
TimerCallback代表負責指定與Timer物件相關聯的方法(就是要週期執行的任務)和狀態。它在方法應得的時間過去之後呼叫一次並且週期性的呼叫這個方法直到呼叫了Dispose方法釋放了Timer的所有資源。系統自動分配分離的執行緒。
讓我們來看一段程式碼看看事如何建立Timer物件和使用它的。我們首先要建立一個TimerCallback代理,在後面的方法中要使用到的。如果需要,下一步我們要建立一個狀態物件,它擁有與被代理呼叫的方法相關聯的特定資訊。為了使這些簡單一些,我們傳遞一個空引數。我們將例項化一個Timer物件,然後再使用Change方法改變Timer的設定,最後呼叫Dispose方法釋放資源。
// class that will be called by the Timer
public class WorkonTimerReq
{
public void aTimerCallMethod()
{
// does some work
}
}
//timer creation block
{
//instantiating the class that gets called by the Timer.
WorkonTimerReq anObj = new WorkonTimerReq () ;
// callback delegate
TimerCallback tcallback = new TimerCallback(anObj. aTimerCallMethod) ;
// define the dueTime and period
long dTime = 20 ; // wait before the first tick (in ms)
long pTime = 150 ; // timer during subsequent invocations (in ms)
// instantiate the Timer object
Timer atimer = new Timer(tcallback, null, dTime, pTime) ;
// do some thing with the timer object
...
//change the dueTime and period of the Timer
dTime=100;
pTime=300;
atimer.Change(dTime, pTime) ;
// do some thing
...
atimer.Dispose() ;
...
}
非同步程式設計
這部分內容如果要講清楚本來就是很大的一部分,在這裡,我不打算詳細討論這個東西,我們只是需要直到它是什麼,因為多執行緒程式設計如果忽律非同步的多執行緒程式設計顯然是不應該的。非同步的多執行緒程式設計是你的程式可能會用到的另外一種多執行緒程式設計方法。
在前面的文章我們花了很大的篇幅來介紹執行緒的同步和怎麼實現執行緒的同步,但是它有一個固有的致命的缺點,你或許注意到了這一點。那就是每個執行緒必須作同步呼叫,也就是等到其他的功能完成,否則就阻塞。當然,某些情況下,對於那些邏輯上相互依賴的任務來說是足夠的。非同步程式設計允許更加複雜的靈活性。一個執行緒可以作非同步呼叫,不需要等待其他的東西。你可以使用這些執行緒作任何的任務,執行緒負責獲取結果推進執行。這給予了那些需要管理數目巨大的請求而且負擔不起請求等待代價的企業級的系統更好的可伸縮性。
.NET平臺提供了一致的非同步程式設計機制用於ASP.NET,I/O,Web Services,Networking,Message等。