本節書摘來自華章出版社《C#多線程編程實戰(原書第2版)》一書中的第3章,第3.2節,作者(美)易格恩·阿格佛溫(Eugene Agafonov),黃博文 黃輝蘭 譯,更多章節內容可以訪問云棲社區“華章計算機”公眾號查看。
3.2 在線程池中調用委托
本節將展示在線程池中如何異步的執行委托。另外,我們將討論一個叫做異步編程模型(Asynchronous Programming Model,簡稱APM)的方式,這是.NET歷史中第一個異步編程模式。
3.2.1 準備工作
為了學習本節,你需要安裝Visual Studio 2015。除此之外無需其他準備。本節的源代碼放置在BookSamplesChapter3Recipe1目錄中。
3.2.2 實現方式
請執行以下步驟來了解如何在線程池中調用委托:
1.啟動Visual Studio 2015。新建一個C#控制臺應用程序項目。
2.在Program.cs文件中加入以下using指令:

3.在Main方法下面加入以下代碼片段:

4.在Main方法中加入以下代碼片段:


5.運行程序。
3.2.3 工作原理
當程序運行時,使用舊的方式創建了一個線程,然后啟動它并等待完成。由于線程的構造函數只接受一個無任何返回結果的方法,我們使用了lambda表達式來將對Test方法的調用包起來。我們通過打印出Thread. CurrentThread.IsThreadPoolThread屬性值來確保該線程不是來自線程池。我們也打印出了受管理的線程ID來識別代碼是被哪個線程執行的。
然后定義了一個委托并調用BeginInvoke方法來運行該委托。BeginInvoke方法接受一個回調函數。該回調函數在異步操作完成后會被調用,并且一個用戶自定義的狀態會傳給該回調函數。該狀態通常用于區分異步調用。結果,我們得到一個實現了IAsyncResult接口的result對象。BeginInvoke立即返回了結果,當線程池中的工作者線程在執行異步操作時,仍允許我們繼續其他工作。當需要異步操作的結果時,可以使用BeginInvoke方法調用返回的result對象。我們可以使用result對象的IsCompleted屬性輪詢結果。但是在本例子中,使用的是AsyncWaitHandle屬性來等待直到操作完成。當操作完成后,會得到一個結果,可以通過委托調用EndInvoke方法,傳遞委托參數和IAsyncResult對象。
事實上使用AsyncWaitHandle并不是必要的。如果注釋掉r.AsyncWaitHandle.WaitOne,代碼照樣可以成功運行,因為EndInvoke方法事實上會等待異步操作完成。調用EndInvoke方法(或者針對其他異步API的EndOperationName方法)是非常重要的,因為該方法會將任何未處理的異常拋回到調用線程中。當使用這種異步API時,請確保始終調用了Begin和End方法。
當操作完成后,傳遞給BeginInvoke方法的回調函數將被放置到線程池中,確切地說是一個工作者線程中。如果在Main方法定義的結尾注釋掉Thread.Sleep方法調用,回調函數將不會被執行。這是因為當主線程完成后,所有的后臺線程會被停止,包括該回調函數。對委托和回調函數的異步調用很可能會被同一個工作者線程執行。通過工作者線程ID可以容易地看出。
使用BeginOperationName/EndOperationName方法和.NET中的IAsyncResult對象等方式被稱為異步編程模型(或APM模式),這樣的方法對稱為異步方法。該模式也被應用于多個.NET類庫的API中,但在現代編程中,更推薦使用任務并行庫(Task Parallel Library,簡稱TPL)來組織異步API。第4章將會討論該主題。