多線程應(yīng)用程序本身是相當不穩(wěn)定的,雖然在.NET平臺下,構(gòu)建一個健壯的多線程程序的困難沒有完全克服,但是這個復(fù)雜的過程還是被大大簡化了。使用System.Threading命名空間中定義的類型,能用最小的代價比較省心地創(chuàng)建額外的線程。同樣,當鎖定共享數(shù)據(jù)時,可以使用多種類型,它們提供了諸如Win32 API線程源語一樣的功能。
當然,System.Threading命名空間并不是構(gòu)建多線程.NET程序的唯一辦法。委托同樣能夠異步調(diào)用成員。開發(fā)者創(chuàng)建線程的原因大多數(shù)是為了讓以無阻塞的(異步)方式調(diào)用方法。雖然可以使用System.Threading命名空間達到類似的效果,但是委托可以使得整個過程大大簡化。
委托
聲明一個委托后,作為響應(yīng),C#編譯器將創(chuàng)建一個派生自System.MulticastDelegate的密封類型。這些基類為每一個委托提供了維護方法地址列表的能力,這些方法可以再以后被調(diào)用。上面那個例子的類差不多是這樣:
public sealed class BinaryOp : System.MulticastDelegate
{
public BinaryOp();
public void Invoke(int x,int y);
public IAsyncResult BeginInvoke(int x,int y,
AsyncCallback cb,object state);
public int EndInvoke(IAsyncResult result);
}
生成的Invoke()方法用來調(diào)用被代理對象用同步方式維護的方法。因此,調(diào)用委托的線程(比如應(yīng)用程序的主線程)將會一直等待,直到委托調(diào)用完成。當然,Invoke()方法并不會直接在代碼中被調(diào)用,而是在使用“正常的”調(diào)用語法時在幕后被觸發(fā)的。
異步委托
有些程序會操作比較長的時間,比如下載一個大文檔,應(yīng)用程序會掛起很長的時間。直到任務(wù)完成以后,這個程序的其他部分才會有響應(yīng)。
如何使委托在單獨的線程上調(diào)用方法,以便模擬多個“同時”運行的任務(wù)?每一個.NET委托類型自動配備了這項能力,而且不需要深入研究System.Threandong命名空間的細節(jié)就能實現(xiàn)。(值得注意的是:為了提高效率,使用BeginInvoke()的時候,CLR并不會創(chuàng)建新的線程,委托的BeginInvoke()方法創(chuàng)建了維護的工作線程池??梢允褂?/font>Threading的ThreadPool類型與之交互)
BeginInvoke()和EndInvoke()、System.IAsyncResult接口
可以看到顯示了不同的線程ID值,消息“Doing more work in Main()!”立即顯示出來,次線程正在忙于業(yè)務(wù)。
不過,我們可以發(fā)現(xiàn),在Begin()和End()之間的是異步的,但是在End之后的主線程還是被阻塞了(其實是Begin完成之前,End調(diào)用線程被阻塞了)。主線程有被阻塞的可能,那么異步委托就毫無優(yōu)勢可言。為了讓線程能夠發(fā)現(xiàn)一部調(diào)用是否完成,IAsyncResult接口提供了IsCompleted屬性。使用這個成員,調(diào)用線程在調(diào)用End之前,便能夠判斷異步調(diào)用是否真的完成。沒有完成可以做其他事情。
IAsyncResult iftAR = b.BeginInvoke(10, 10, null, null);
// 直到Add()方法完成,消息才會顯示出來
while (!iftAR.IsCompleted)
{
Console.WriteLine("Doing more work in Main()!");
// 這里只是為了等待1秒,防止輸出太多
Thread.Sleep(1000);
}
// 知道Add已經(jīng)完成
int answer = b.EndInvoke(iftAR);
除了IsCompleted屬性,接口還提供了AsyncWaitHandle屬性以便實現(xiàn)更加靈活的阻塞控制。這個屬性返回一個WaitHandle類型的實例,它有一個名為waitOne()的方法。使用這個方法可以指定等待時間。如果超時,waitOne()返回false。
這就像不重不斷詢問的方式,并不是很高效,委托還提供了另外的技術(shù)來獲取異步調(diào)用結(jié)果。
AsynCallback、AsyncResult、傳遞自定義數(shù)據(jù)
通過輪詢的方式來確定調(diào)用的方法執(zhí)行是否結(jié)束,這種方式并不好。我們可以在任務(wù)完成的時候由次線程主動通知調(diào)用線程的方式。要使用這種方式,需要在BeginInvoke()時提供一個System.AsyncCallback委托的實例作為參數(shù)。只要提供了這個對象,當一部調(diào)用完成的時候,委托便會自動調(diào)用(AsyncCallback對象)指定的方法。
static void MyAsyncCallbackMethod(IAsyncResult itfAR)
代碼如下:
但是在AsyncCallback中,我們不能訪問AsyncCallback委托目標(這里指
AddComplete)無法訪問BinaryOp委托(把BinaryOp改成靜態(tài)的并不是很好的辦法)。這里我們可以采用IAsyncResult 輸入?yún)?shù)。被傳入AsyncCallback委托目標中的IAsyncnResult參數(shù),其實是System.Runtime.Remoting.Messaging命名空間下的AsyncResult類。該類的靜態(tài)屬性AsyncDelegate返回了別處創(chuàng)建的原始異步委托的引用。
異步委托的最后一個參數(shù),允許從主線程傳遞額外的狀態(tài)信息給回調(diào)方法,類型是Object。所以可以傳遞任何希望的數(shù)據(jù),比如:
static void AddComplete(IAsyncResult itfAR)
{
Console.WriteLine("AddComplete() 執(zhí)行在線程on thread {0}.",
Thread.CurrentThread.ManagedThreadId);
// 得到結(jié)果
AsyncResult ar = (AsyncResult)itfAR;
BinaryOp b = (BinaryOp)ar.AsyncDelegate;
Console.WriteLine("10 + 10 is {0}.", b.EndInvoke(itfAR));
}