本文轉載自:http://www.cnblogs.com/94cool/archive/2010/04/08/1707637.html
計算機中有些處理比較耗時。調用這種處理代碼時,調用方如果站在那里苦苦等待,會嚴重影響程序性能。例如,某個程序啟動后如果需要打開文件讀出其中的數據,再根據這些數據進行一系列初始化處理,程序主窗口將遲遲不能顯示,讓用戶感到這個程序怎么等半天也不出來,太差勁了。借助異步調用可以把問題輕松化解:把整個初始化處理放進一個單獨線程,主線程啟動此線程后接着往下走,讓主窗口瞬間顯示出來。等用戶盯着窗口犯呆時,初始化處理就在背后悄悄完成了。程序開始穩定運行以后,還可以繼續使用這種技巧改善人機交互的瞬時反應。用戶點擊鼠標時,所激發的操作如果較費時,再點擊鼠標將不會立即反應,整個程序顯得很沉重。借助異步調用處理費時的操作,讓主線程隨時恭候下一條消息,用戶點擊鼠標時感到輕松快捷,肯定會對軟件產生好感。
異步調用用來處理從外部輸入的數據特別有效。假如計算機需要從一台低速設備索取數據,然后是一段冗長的數據處理過程,采用同步調用顯然很不合算:計算機先向外部設備發出請求,然后等待數據輸入;而外部設備向計算機發送數據后,也要等待計算機完成數據處理后再發出下一條數據請求。雙方都有一段等待期,拉長了整個處理過程。其實,計算機可以在處理數據之前先發出下一條數據請求,然后立即去處理數據。如果數據處理比數據采集快,要等待的只有計算機,外部設備可以連續不停地采集數據。如果計算機同時連接多台輸入設備,可以輪流向各台設備發出數據請求,並隨時處理每台設備發來的數據,整個系統可以保持連續高速運轉。編程的關鍵是把數據索取代碼和數據處理代碼分別歸屬兩個不同的線程。數據處理代碼調用一個數據請求異步函數,然后徑自處理手頭的數據。待下一組數據到來后,數據處理線程將收到通知,結束 wait 狀態,發出下一條數據請求,然后繼續處理數據。
異步調用時,調用方不等被調方返回結果就轉身離去,因此必須有一種機制讓被調方有了結果時能通知調用方。在同一進程中有很多手段可以利用,筆者常用的手段是回調、event 對象和消息。
回調方式很簡單:調用異步函數時在參數中放入一個函數地址,異步函數保存此地址,待有了結果后回調此函數便可以向調用方發出通知。如果把異步函數包裝進一個對象中,可以用事件取代回調函數地址,通過事件處理例程向調用方發通知。
event 是 windows 系統提供的一個常用同步對象,以在異步處理中對齊不同線程之間的步點。如果調用方暫時無事可做,可以調用 wait 函數等在那里,此時 event 處於 nonsignaled 狀態。當被調方出來結果之后,把 event 對象置於 signaled 狀態,wait 函數便自動結束等待,使調用方重新動作起來,從被調方取出處理結果。這種方式比回調方式要復雜一些,速度也相對較慢,但有很大的靈活性,可以搞出很多花樣以適應比較復雜的處理系統。
借助 windows 消息發通知是個不錯的選擇,既簡單又安全。程序中定義一個用戶消息,並由調用方准備好消息處理例程。被調方出來結果之后立即向調用方發送此消息,並通過 wparam 和 lparam 這兩個參數傳送結果。消息總是與窗口 handle 關聯,因此調用方必須借助一個窗口才能接收消息,這是其不方便之處。另外,通過消息聯絡會影響速度,需要高速處理時回調方式更有優勢。
如果調用方和被調方分屬兩個不同的進程,由於內存空間的隔閡,一般是采用 windows 消息發通知比較簡單可靠,被調方可以借助消息本身向調用方傳送數據。event 對象也可以通過名稱在不同進程間共享,但只能發通知,本身無法傳送數據,需要借助 windows 消息和 filemapping 等內存共享手段或借助 mailslot 和 pipe 等通信手段。
異步調用原理並不復雜,但實際使用時容易出莫名其妙的問題,特別是不同線程共享代碼或共享數據時容易出問題,編程時需要時時注意是否存在這樣的共享,並通過各種狀態標志避免沖突。windows 系統提供的 mutex 對象用在這里特別方便。mutex 同一時刻只能有一個管轄者。一個線程放棄管轄權后,另一線程才能接管。當某線程執行到敏感區之前先接管 mutex,使其他線程被 wait 函數堵在身后;脫離敏感區之后立即放棄管轄權,使 wait 函數結束等待,另一個線程便有機會光臨此敏感區。這樣就可以有效避免多個線程進入同一敏感區。
由於異步調用容易出問題,要設計一個安全高效的編程方案需要比較多的設計經驗,所以最好不要濫用異步調用。同步調用畢竟讓人更舒服些:不管程序走到哪里,只要死盯着移動點就能心中有數,不至於象異步調用那樣,總有一種四面受敵、惶惶不安的感覺。必要時甚至可以把異步函數轉換為同步函數。方法很簡單:調用異步函數后馬上調用 wait 函數等在那里,待異步函數返回結果后再繼續往下走。
C#提供了異步方法調用的功能,先創建一個委托,該委托的簽名要與需要異步執行的方法定義相匹配。還是以代碼來說明。
委托的聲明:
public delegate string AsyncDelegateGetPage( Uri uri , out string url );
調用方代碼:
AsyncCallback callback = new AsyncCallback( ProcessPage ); //回調函數聲明
AsyncDelegateGetPage ad = new AsyncDelegateGetPage( GetPageSource );//實例化委托類型
IAsyncResult ar = ad.BeginInvoke( this.ObtainWork() ,out url, callback , ad );//開始調用
ar.AsyncWaitHandle.WaitOne();//阻塞主線程,直到異步完成,用於多線程同步,一般可以不需要。
BeginInvoke方法為開始異步調用,其參數為動態的,依據委托的簽名。上面的情況,參數為:Uri , out url,回調函數實例(可為null),委托實例(可為null)。即前面的幾個參數為委托方法的參數,后面2個分別是回調函數實例和委托實例。參數委托實例用於將該實例傳遞到回調函數中。注意回調函數的必須為void ,並且參數為IAsyncResult 類型。
委托的方法代碼:
public String GetPageSource( Uri uri , out string url ){
url = uri.ToString();
return this.GetPageSource( uri );
}
回調函數代碼:
void ProcessPage( IAsyncResult ar ){
AsyncDelegateGetPage andl = (AsyncDelegateGetPage)ar.AsyncState;
string url;
string source = andl.EndInvoke( out url, ar );
}
委托類型的EndInvoke方法,參數為異步結果,IAsyncResult類型。執行該方法后,返回異步調用的結果。
以上是針對有回調函數的情況。
附:
C#異步調用四大方法是什么呢?C#異步調用四大方法的使用是如何進行的呢?讓我們首先了解下什么時候用到C#異步調用:
.NET Framework 允許您C#異步調用任何方法。定義與您需要調用的方法具有相同簽名的委托;公共語言運行庫將自動為該委托定義具有適當簽名的 BeginInvoke 和 EndInvoke 方法。
BeginInvoke 方法用於啟動C#異步調用。它與您需要異步執行的方法具有相同的參數,只不過還有兩個額外的參數(將在稍后描述)。BeginInvoke 立即返回,不等待C#異步調用完成。BeginInvoke 返回 IasyncResult,可用於監視調用進度。
EndInvoke 方法用於檢索C#異步調用結果。調用 BeginInvoke 后可隨時調用 EndInvoke 方法;如果C#異步調用未完成,EndInvoke 將一直阻塞到C#異步調用完成。EndInvoke 的參數包括您需要異步執行的方法的 out 和 ref 參數(在 Visual Basic 中為 ByRef 和 ByRef)以及由 BeginInvoke 返回的 IAsyncResult。
注意 Visual Studio .NET 中的智能感知功能會顯示 BeginInvoke 和 EndInvoke 的參數。如果您沒有使用 Visual Studio 或類似的工具,或者您使用的是 C# 和 Visual Studio .NET,請參見異步方法簽名獲取有關運行庫為這些方法定義的參數的描述。
本主題中的代碼演示了四種使用 BeginInvoke 和 EndInvoke 進行C#異步調用的常用方法。調用了 BeginInvoke 后,可以:
· 進行某些操作,然后調用 EndInvoke 一直阻塞到調用完成。
· 使用 IAsyncResult.AsyncWaitHandle 獲取 WaitHandle,使用它的 WaitOne 方法將執行一直阻塞到發出 WaitHandle 信號,然后調用 EndInvoke。
· 輪詢由 BeginInvoke 返回的 IAsyncResult,確定C#異步調用何時完成,然后調用 EndInvoke。
· 將用於回調方法的委托傳遞給 BeginInvoke。該方法在C#異步調用完成后在 ThreadPool 線程上執行,它可以調用 EndInvoke。
警告:始終在C#異步調用完成后調用 EndInvoke。
測試方法和異步委托
四個示例全部使用同一個長期運行的測試方法 TestMethod。該方法顯示一個表明它已開始處理的控制台信息,休眠幾秒鍾,然后結束。TestMethod 有一個 out 參數(在 Visual Basic 中為 ByRef),它演示了如何將這些參數添加到 BeginInvoke 和 EndInvoke 的簽名中。您可以用類似的方式處理 ref 參數(在 Visual Basic 中為 ByRef)。
下面的代碼示例顯示 TestMethod 以及代表 TestMethod 的委托;若要使用任一示例,請將示例代碼追加到這段代碼中。
注意 為了簡化這些示例,TestMethod 在獨立於 Main() 的類中聲明。或者,TestMethod 可以是包含 Main() 的同一類中的 static 方法(在 Visual Basic 中為 Shared)。
- using System;
- using System.Threading;
- public class AsyncDemo {
- // The method to be executed asynchronously.
- //
- public string TestMethod(
- int callDuration, out int threadId) {
- Console.WriteLine("Test method begins.");
- Thread.Sleep(callDuration);
- threadId = AppDomain.GetCurrentThreadId();
- return "MyCallTime was " + callDuration.ToString();
- }
- }
- // The delegate must have the same signature as the method
- // you want to call asynchronously.
- public delegate string AsyncDelegate(
- int callDuration, out int threadId);
- using System;
- using System.Threading;
- public class AsyncDemo {
- // The method to be executed asynchronously.
- //
- public string TestMethod(
- int callDuration, out int threadId) {
- Console.WriteLine("Test method begins.");
- Thread.Sleep(callDuration);
- threadId = AppDomain.GetCurrentThreadId();
- return "MyCallTime was " + callDuration.ToString();
- }
- }
- // The delegate must have the same signature as the method
- // you want to call asynchronously.
- public delegate string AsyncDelegate(
- int callDuration, out int threadId);
C#異步調用四大方法之使用 EndInvoke 等待異步調用
異步執行方法的最簡單方式是以 BeginInvoke 開始,對主線程執行一些操作,然后調用 EndInvoke。EndInvoke 直到C#異步調用完成后才返回。這種技術非常適合文件或網絡操作,但是由於它阻塞 EndInvoke,所以不要從用戶界面的服務線程中使用它。
- public class AsyncMain {
- static void Main(string[] args) {
- // The asynchronous method puts the thread id here.
- int threadId;
- // Create an instance of the test class.
- AsyncDemo ad = new AsyncDemo();
- // Create the delegate.
- AsyncDelegate dlgt = new AsyncDelegate(ad.TestMethod);
- // Initiate the asychronous call.
- IAsyncResult ar = dlgt.BeginInvoke(3000,
- out threadId, null, null);
- Thread.Sleep(0);
- Console.WriteLine("Main thread {0} does some work.",
- AppDomain.GetCurrentThreadId());
- // Call EndInvoke to Wait for
- //the asynchronous call to complete,
- // and to retrieve the results.
- string ret = dlgt.EndInvoke(out threadId, ar);
- Console.WriteLine("The call executed on thread {0},
- with return value \"{1}\".", threadId, ret);
- }
- }
C#異步調用四大方法之使用 WaitHandle 等待異步調用
等待 WaitHandle 是一項常用的線程同步技術。您可以使用由 BeginInvoke 返回的 IAsyncResult 的 AsyncWaitHandle 屬性來獲取 WaitHandle。C#異步調用完成時會發出 WaitHandle 信號,而您可以通過調用它的 WaitOne 等待它。
如果您使用 WaitHandle,則在C#異步調用完成之后,但在通過調用 EndInvoke 檢索結果之前,可以執行其他處理。
- public class AsyncMain {
- static void Main(string[] args) {
- // The asynchronous method puts the thread id here.
- int threadId;
- // Create an instance of the test class.
- AsyncDemo ad = new AsyncDemo();
- // Create the delegate.
- AsyncDelegate dlgt = new AsyncDelegate(ad.TestMethod);
- // Initiate the asychronous call.
- IAsyncResult ar = dlgt.BeginInvoke(3000,
- out threadId, null, null);
- Thread.Sleep(0);
- Console.WriteLine("Main thread {0} does some work.",
- AppDomain.GetCurrentThreadId());
- // Wait for the WaitHandle to become signaled.
- ar.AsyncWaitHandle.WaitOne();
- // Perform additional processing here.
- // Call EndInvoke to retrieve the results.
- string ret = dlgt.EndInvoke(out threadId, ar);
- Console.WriteLine("The call executed on thread {0},
- with return value \"{1}\".", threadId, ret);
- }
- }
C#異步調用四大方法之輪詢異步調用完成
您可以使用由 BeginInvoke 返回的 IAsyncResult 的 IsCompleted 屬性來發現C#異步調用何時完成。從用戶界面的服務線程中進行C#異步調用時可以執行此操作。輪詢完成允許用戶界面線程繼續處理用戶輸入。
- public class AsyncMain {
- static void Main(string[] args) {
- // The asynchronous method puts the thread id here.
- int threadId;
- // Create an instance of the test class.
- AsyncDemo ad = new AsyncDemo();
- // Create the delegate.
- AsyncDelegate dlgt = new AsyncDelegate(ad.TestMethod);
- // Initiate the asychronous call.
- IAsyncResult ar = dlgt.BeginInvoke(3000,
- out threadId, null, null);
- // Poll while simulating work.
- while(ar.IsCompleted == false) {
- Thread.Sleep(10);
- }
- // Call EndInvoke to retrieve the results.
- string ret = dlgt.EndInvoke(out threadId, ar);
- Console.WriteLine("The call executed on thread {0},
- with return value \"{1}\".", threadId, ret);
- }
- }
C#異步調用四大方法之異步調用完成時執行回調方法
如果啟動異步調用的線程不需要處理調用結果,則可以在調用完成時執行回調方法。回調方法在 ThreadPool 線程上執行。
要使用回調方法,必須將代表該方法的 AsyncCallback 委托傳遞給 BeginInvoke。也可以傳遞包含回調方法將要使用的信息的對象。例如,可以傳遞啟動調用時曾使用的委托,以便回調方法能夠調用 EndInvoke。
- public class AsyncMain {
- // Asynchronous method puts the thread id here.
- private static int threadId;
- static void Main(string[] args) {
- // Create an instance of the test class.
- AsyncDemo ad = new AsyncDemo();
- // Create the delegate.
- AsyncDelegate dlgt = new AsyncDelegate(ad.TestMethod);
- // Initiate the asychronous call. Include an AsyncCallback
- // delegate representing the callback method, and the data
- // needed to call EndInvoke.
- IAsyncResult ar = dlgt.BeginInvoke(3000,
- out threadId,
- new AsyncCallback(CallbackMethod),
- dlgt );
- Console.WriteLine("Press Enter to close application.");
- Console.ReadLine();
- }
- // Callback method must have the same signature as the
- // AsyncCallback delegate.
- static void CallbackMethod(IAsyncResult ar) {
- // Retrieve the delegate.
- AsyncDelegate dlgt = (AsyncDelegate) ar.AsyncState;
- // Call EndInvoke to retrieve the results.
- string ret = dlgt.EndInvoke(out threadId, ar);
- Console.WriteLine("The call executed on thread {0},
- with return value \"{1}\".", threadId, ret);
- }
- }
C#異步調用四大方法的基本內容就向你介紹到這里,希望對你了解和學習C#異步調用有所幫助。