.NET Framework 允許異步調用任何方法,為了實現異步調用目標,需要定義與被調用方法具有相同簽名的委托。公共語言運行時會自動使用適當的簽名為該委托定義 BeginInvoke 和 EndInvoke 方法,也就是說委托的 BeginInvoke 和 EndInvoke 方法是自動生成的,無需定義。所謂的異步調用,指的是在新線程中執行被調用的方法。
BeginInvoke 方法啟動異步調用, 該方法與要異步執行的方法具有相同的參數,還有另外兩個可選參數。第一個參數是一個 AsyncCallback 委托,該委托引用在異步調用完成時要調用的方法。 第二個參數是一個用戶定義的對象,該對象將信息傳遞到回調方法。BeginInvoke 立即返回,不等待異步調用完成。 BeginInvoke 返回一個 IAsyncResult,后者可用於監視異步調用的進度。
采用BeginInvoke 和 EndInvoke實現方法的異步調用共有四種方式,下面用演示代碼分別說明。代碼示例演示異步調用一個長時間運行的方法 TestMethod 的各種方式。 TestMethod 方法會顯示一條控制台消息,說明該方法已開始處理,方法所在的線程,休眠了幾秒鍾,然后結束。 TestMethod 有一個 out 參數,用於說明此類參數添加到 BeginInvoke 和EndInvoke 的簽名中的方式,可以按同樣的方式處理 ref 參數。
public static string TestMethod(int callDuration, out int threadId) { Console.WriteLine("方法的執行線程ID: {0}", Thread.CurrentThread.ManagedThreadId); Thread.Sleep(callDuration); threadId = Thread.CurrentThread.ManagedThreadId; return String.Format("My call time was {0}.", callDuration.ToString()); }
定義相應的委托類型
public delegate string AsyncDelegate(int callDuration, out int threadId);
1 使用 EndInvoke阻塞調用線程,直到異步調用結束
異步執行方法的最簡單方式是通過調用委托的 BeginInvoke 方法來開始執行方法,可以在在調用線程上執行一些其他操作,然后調用委托的 EndInvoke 方法。 EndInvoke 會阻塞調用線程,直到異步調用完成后才返回。
namespace TestInvoke { public delegate string AsyncDelegate(int callDuration, out int threadId); class Program { static void Main(string[] args) { //輸出主線程ID Console.WriteLine("主線程ID:{0}", Thread.CurrentThread.ManagedThreadId); //創建委托 AsyncDelegate asyncDel = new AsyncDelegate(TestMethod); int nThreadID = 0; //異步執行TestMethod方法 IAsyncResult result = asyncDel.BeginInvoke(3000, out nThreadID, null, null); //阻塞調用線程 asyncDel.EndInvoke(out nThreadID, result); Console.ReadKey(); } public static string TestMethod(int callDuration, out int threadId) { Console.WriteLine("方法的執行線程ID: {0}", Thread.CurrentThread.ManagedThreadId); Thread.Sleep(callDuration); threadId = Thread.CurrentThread.ManagedThreadId; return String.Format("My call time was {0}.", callDuration.ToString()); } } }
調用線程會被阻塞3秒鍾,程序輸出為:
2 使用 WaitHandle 等待異步調用
使用由 BeginInvoke 返回的 IAsyncResult 的 AsyncWaitHandle 屬性來獲取 WaitHandle。 異步調用完成時, WaitHandle 會收到信號,可以通過調用 WaitOne 方法等待它。
如果您使用 WaitHandle時,在調用 EndInvoke 檢索結果之前,還可以執行其他處理。
static void Main(string[] args) { //輸出主線程ID Console.WriteLine("主線程ID:{0}", Thread.CurrentThread.ManagedThreadId); //創建委托 AsyncDelegate asyncDel = new AsyncDelegate(TestMethod); int nThreadID = 0; //異步執行TestMethod方法 IAsyncResult result = asyncDel.BeginInvoke(3000, out nThreadID, null, null); //阻塞調用線程 WaitHandle handle = result.AsyncWaitHandle; handle.WaitOne(); //其他操作 //終止異步調用,通過返回值取得調用結果 string returnValue = asyncDel.EndInvoke(out nThreadID, result); Console.WriteLine("The call executed on thread {0}, with return value \"{1}\".", nThreadID, returnValue); Console.ReadKey(); }
3輪詢異步調用完成
可以使用由 BeginInvoke 返回的 IAsyncResult 的 IsCompleted 屬性來判斷異步調用何時完成。
static void Main(string[] args) { //輸出主線程ID Console.WriteLine("主線程ID:{0}", Thread.CurrentThread.ManagedThreadId); //創建委托 AsyncDelegate asyncDel = new AsyncDelegate(TestMethod); int nThreadID = 0; //異步執行TestMethod方法 IAsyncResult result = asyncDel.BeginInvoke(3000, out nThreadID, null, null); //輪詢異步執行狀態 while (true) { if (result.IsCompleted) { break; } Thread.Sleep(1000); } //終止異步調用,通過返回值取得調用結果 string returnValue = asyncDel.EndInvoke(out nThreadID, result); Console.WriteLine("The call executed on thread {0}, with return value \"{1}\".", nThreadID, returnValue); Console.ReadKey(); }
4 異步調用完成時執行回調方法
如果調用的線程不需要的異步執行函數的返回值,則可以在調用完成時執行回調方法,回調方法在 ThreadPool 里的異步線程上執行。
若要使用回調方法,必須將表示回調方法的 AsyncCallback 委托傳遞給 BeginInvoke。 也可以傳遞包含回調方法要使用的信息的對象。在回調方法中,可以將 IAsyncResult(回調方法的唯一參數)強制轉換為 AsyncResult 對象。 然后,可以使用AsyncResult .AsyncDelegate 屬性獲取已用於啟動調用的委托,以便可以調用 EndInvoke。
-
TestMethod 的 threadId 參數為 out 參數,因此 TestMethod 從不使用該參數的輸入值。 如果 threadId 參數為 ref 參數,則該變量必須為類級別字段,這樣才能同時傳遞給 BeginInvoke 和 EndInvoke。
-
傳遞給 BeginInvoke 的狀態信息是一個格式字符串,所以狀態信息必須強制轉換為正確的類型才能被使用。
-
回調在 ThreadPool 線程上執行。 ThreadPool 線程是后台線程,這些線程不會在主線程結束后保持應用程序的運行,因此示例的主線程必須休眠足夠長的時間以便回調完成。
static void Main(string[] args) { //輸出主線程ID Console.WriteLine("主線程ID:{0}", Thread.CurrentThread.ManagedThreadId); //創建委托 AsyncDelegate asyncDel = new AsyncDelegate(TestMethod); int nThreadID = 0; //異步執行TestMethod方法,使用回調函數並傳入state參數 IAsyncResult result = asyncDel.BeginInvoke(3000, out nThreadID, new AsyncCallback(AsyncCallCompleted), "測試參數傳遞"); Console.ReadKey(); } public static void AsyncCallCompleted(IAsyncResult ar) { Console.WriteLine("AsyncCallCompleted執行線程ID:{0}",Thread.CurrentThread.ManagedThreadId); //獲取委托對象 System.Runtime.Remoting.Messaging.AsyncResult result = (System.Runtime.Remoting.Messaging.AsyncResult)ar; AsyncDelegate asyncDel = (AsyncDelegate)result.AsyncDelegate; //獲取BeginInvoke傳入的的state參數 string strState = (string)ar.AsyncState; Console.WriteLine("傳入的字符串是{0}",strState); //結束異步調用 int nThreadID; string strResult = asyncDel.EndInvoke(out nThreadID, ar); Console.WriteLine("\nThe call executed on thread {0}, with return value \"{1}\".", nThreadID, strResult); } public static string TestMethod(int callDuration, out int threadId) { Console.WriteLine("TestMethod方法的執行線程ID: {0}", Thread.CurrentThread.ManagedThreadId); Thread.Sleep(callDuration); threadId = Thread.CurrentThread.ManagedThreadId; return String.Format("My call time was {0}.", callDuration.ToString()); }
輸出結果為
總結
除了第4種采用回調函數的方式外,其他三種方式均會阻塞調用線程。