【转】1.7异步编程:基于事件的异步编程模式(EAP)
傳送門:異步編程系列目錄……
上一篇,我給大家介紹了“.NET1.0?IAsyncResult異步編程模型(APM)”,通過Begin***?開啟操作并返回IAsyncResult對象,使用?End***?方法來結(jié)束操作,通過回調(diào)方法來做異步操作后其它事項。然而最大的問題是沒有提供進度通知等功能及多線程間控件的訪問。為克服這個問題(并解決其他一些問題),.NET2.0?中引入了:基于事件的異步編程模式(EAP,Event-based Asynchronous Pattern)。通過事件、AsyncOperationManager類和AsyncOperation類兩個幫助器類實現(xiàn)如下功能:
1)???異步執(zhí)行耗時的任務(wù)。
2)???獲得進度報告和增量結(jié)果。
3)???支持耗時任務(wù)的取消。
4)???獲得任務(wù)的結(jié)果值或異常信息。
5)???更復(fù)雜:支持同時執(zhí)行多個異步操作、進度報告、增量結(jié)果、取消操作、返回結(jié)果值或異常信息。
對于相對簡單的多線程應(yīng)用程序,BackgroundWorker組件提供了一個簡單的解決方案。對于更復(fù)雜的異步應(yīng)用程序,可以考慮實現(xiàn)一個符合基于事件的異步模式的類。
?
?
?
源碼下載:異步編程:基于事件的異步模型(EAP).rar
?
EAP異步編程模型的優(yōu)點
????EAP是為Windows窗體開發(fā)人員創(chuàng)建的,其主要優(yōu)點在于:
1.???EAP與Microsoft Visual Studio UI設(shè)計器進行了很好的集成。也就是說,可將大多數(shù)實現(xiàn)了EAP的類拖放到一個Visual Studio設(shè)計器平面上,然后雙擊事件名,讓Visual Studio自動生成事件回調(diào)方法,并將方法同事件關(guān)聯(lián)起來。
2.???EAP類在內(nèi)部通過SynchronizationContext類,將應(yīng)用程序模型映射到合適線程處理模型,以方便跨線程操作控件。
?
為了實現(xiàn)基于事件的異步模式,我們必須先理解兩個重要的幫助器類:
?
AsyncOperationManager和AsyncOperation
????AsyncOperationManager類和AsyncOperation類是System.ComponentModel命名空間為我們提供了兩個重要幫助器類。在基于事件的異步模式封裝標準化的異步功能中,它確保你的異步操作支持在各種應(yīng)用程序模型(包括?ASP.NET、控制臺應(yīng)用程序和?Windows?窗體應(yīng)用程序)的適當(dāng)“線程或上下文”調(diào)用客戶端事件處理程序。
AsyncOperationManager類和AsyncOperation類的API如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | // 為支持異步方法調(diào)用的類提供并發(fā)管理。此類不能被繼承。 public?static?class?AsyncOperationManager { ????// 獲取或設(shè)置用于異步操作的同步上下文。 ????public?static?SynchronizationContext SynchronizationContext { get; set; } ? ????// 返回可用于對特定異步操作的持續(xù)時間進行跟蹤的AsyncOperation對象。 ????// 參數(shù):userSuppliedState: ????//???? 一個對象,用于使一個客戶端狀態(tài)(如任務(wù) ID)與一個特定異步操作相關(guān)聯(lián)。 ????public?static?AsyncOperation CreateOperation(object?userSuppliedState) ????{ ????????return?AsyncOperation.CreateOperation(userSuppliedState,SynchronizationContext); ????} } ? // 跟蹤異步操作的生存期。 public?sealed?class?AsyncOperation { ????// 構(gòu)造函數(shù) ????private?AsyncOperation(object?userSuppliedState, SynchronizationContext syncContext); ????internal?static?AsyncOperation CreateOperation(object?userSuppliedState ????????????????????????????????????????????, SynchronizationContext syncContext); ? ????// 獲取傳遞給構(gòu)造函數(shù)的SynchronizationContext對象。 ????public?SynchronizationContext SynchronizationContext { get; } ????// 獲取或設(shè)置用于唯一標識異步操作的對象。 ????public?object?UserSuppliedState { get; } ? ????// 在各種應(yīng)用程序模型適合的線程或上下文中調(diào)用委托。 ????public?void?Post(SendOrPostCallback d, object?arg); ????// 結(jié)束異步操作的生存期。 ????public?void?OperationCompleted(); ????// 效果同調(diào)用 Post() + OperationCompleted() 方法組合 ????public?void?PostOperationCompleted(SendOrPostCallback d, object?arg); } |
????先分析下這兩個幫助器類:
1.???AsyncOperationManager是靜態(tài)類。靜態(tài)類是密封的,因此不可被繼承。倘若從靜態(tài)類繼承會報錯“靜態(tài)類必須從?Object?派生”。(小常識,以前以為密封類就是?sealed?關(guān)鍵字)
2.???AsyncOperationManager為支持異步方法調(diào)用的類提供并發(fā)管理,該類可正常運行于?.NET Framework?支持的所有應(yīng)用程序模式下。
3.???AsyncOperation實例提供對特定異步任務(wù)的生存期進行跟蹤。可用來處理任務(wù)完成通知,還可用于在不終止異步操作的情況下發(fā)布進度報告和增量結(jié)果(這種不終止異步操作的處理是通過AsyncOperation的?Post()?方法實現(xiàn))。
4.???AsyncOperation類有一個私有的構(gòu)造函數(shù)和一個內(nèi)部CreateOperation()?靜態(tài)方法。由AsyncOperationManager類調(diào)用AsyncOperation.CreateOperation()?靜態(tài)方法來創(chuàng)建AsyncOperation實例。
5.???AsyncOperation類是通過SynchronizationContext類來實現(xiàn)在各種應(yīng)用程序的適當(dāng)“線程或上下文”調(diào)用客戶端事件處理程序。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // 提供在各種同步模型中傳播同步上下文的基本功能。 public?class?SynchronizationContext { ????// 獲取當(dāng)前線程的同步上下文。 ????public?static?SynchronizationContext Current { get; } ? ????// 當(dāng)在派生類中重寫時,響應(yīng)操作已開始的通知。 ????public?virtual?void?OperationStarted(); ????// 當(dāng)在派生類中重寫時,將異步消息調(diào)度到一個同步上下文。 ????public?virtual?void?Post(SendOrPostCallback d, object?state); ????// 當(dāng)在派生類中重寫時,響應(yīng)操作已完成的通知。 ????public?virtual?void?OperationCompleted(); ????…… } |
a)???在AsyncOperation構(gòu)造函數(shù)中調(diào)用SynchronizationContext的OperationStarted()?;
b)???????在AsyncOperation的?Post()?方法中調(diào)用SynchronizationContext的Post()?;
c)???在AsyncOperation的OperationCompleted()方法中調(diào)用SynchronizationContext的OperationCompleted();
6.???SendOrPostCallback委托簽名:
????//?表示在消息即將被調(diào)度到同步上下文時要調(diào)用的方法。
public delegate void SendOrPostCallback(object state);
???
基于事件的異步模式的特征
1.???基于事件的異步模式可以采用多種形式,具體取決于某個特定類支持操作的復(fù)雜程度:
1)???最簡單的類可能只有一個?***Async方法和一個對應(yīng)的?***Completed?事件,以及這些方法的同步版本。
2)???復(fù)雜的類可能有若干個?***Async方法,每種方法都有一個對應(yīng)的?***Completed?事件,以及這些方法的同步版本。
3)???更復(fù)雜的類還可能為每個異步方法支持取消(CancelAsync()方法)、進度報告和增量結(jié)果(ReportProgress()?方法+ProgressChanged事件)。
4)???如果您的類支持多個異步方法,每個異步方法返回不同類型的數(shù)據(jù),您應(yīng)該:
a)???將您的增量結(jié)果報告與您的進度報告分開。
b)???使用適當(dāng)?shù)腅ventArgs為每個異步方法定義一個單獨的?***ProgressChanged事件以處理該方法的增量結(jié)果數(shù)據(jù)。
5)???如果類不支持多個并發(fā)調(diào)用,請考慮公開IsBusy屬性。
6)???如要異步操作的同步版本中有?Out?和?Ref?參數(shù),它們應(yīng)做為對應(yīng)?***CompletedEventArgs的一部分,eg:
| 1 2 3 4 5 6 7 8 9 | public?int?MethodName(string?arg1, ref?string?arg2, out?string?arg3); ? public?void?MethodNameAsync(string?arg1, string?arg2); public?class?MethodNameCompletedEventArgs : AsyncCompletedEventArgs { ????public?int?Result { get; }; ????public?string?Arg2 { get; }; ????public?string?Arg3 { get; }; } |
2.???如果你的組件要支持多個異步耗時的任務(wù)并行執(zhí)行。那么:
1)???為***Async方法多添加一個userState對象參數(shù)(此參數(shù)應(yīng)當(dāng)始終是***Async方法簽名中的最后一個參數(shù)),用于跟蹤各個操作的生存期。
2)???注意要在你構(gòu)建的異步類中維護一個userState對象的集合。使用?lock?區(qū)域保護此集合,因為各種調(diào)用都會在此集合中添加和移除userState對象。
3)???在***Async方法開始時調(diào)用AsyncOperationManager.CreateOperation并傳入userState對象,為每個異步任務(wù)創(chuàng)建AsyncOperation對象,userState存儲在AsyncOperation的UserSuppliedState屬性中。在構(gòu)建的異步類中使用該屬性標識取消的操作,并傳遞給CompletedEventArgs和ProgressChangedEventArgs參數(shù)的UserState屬性來標識當(dāng)前引發(fā)進度或完成事件的特定異步任務(wù)。
4)???當(dāng)對應(yīng)于此userState對象的任務(wù)引發(fā)完成事件時,你構(gòu)建的異步類應(yīng)將AsyncCompletedEventArgs.UserState對象從集合中刪除。
3.???異常處理
EAP的錯誤處理和系統(tǒng)的其余部分不一致。首先,異常不會拋出。在你的事件處理方法中,必須查詢AsyncCompletedEventArgs的Exception屬性,看它是不是null。如果不是null,就必須使用if語句判斷Exception派生對象的類型,而不是使用catch塊。
另外,如果你的代碼忽略錯誤,那么不會發(fā)生未處理的異常,錯誤會變得未被檢測到,應(yīng)用程序?qū)⒗^續(xù)運行,其結(jié)果不可預(yù)知。
4.???注意:
1)???確保?***EventArgs類特定于***方法。即當(dāng)使用?***EventArgs類時,切勿要求開發(fā)人員強制轉(zhuǎn)換類型值。
2)???確保始終引發(fā)方法名稱Completed?事件。成功完成、異常或者取消時應(yīng)引發(fā)此事件。任何情況下,應(yīng)用程序都不應(yīng)遇到這樣的情況:應(yīng)用程序保持空閑狀態(tài),而操作卻一直不能完成。
3)???確保可以捕獲異步操作中發(fā)生的任何異常并將捕獲的異常指派給?Error?屬性。
4)???確保?***CompletedEventArgs?類將其成員公開為只讀屬性而不是字段,因為字段會阻止數(shù)據(jù)綁定。eg:public MyReturnType Result { get; }
5)???在構(gòu)建?***CompletedEventArgs?類屬性時,通過this.RaiseExceptionIfNecessary()?方法確保屬性值被正確使用。Eg:
| 1 2 3 4 5 6 7 8 9 | private?bool?isPrimeValue; public?bool?IsPrime { ????get ????{ ????????RaiseExceptionIfNecessary(); ????????return?isPrimeValue; ????} } |
所以,在***Completed事件處理程序中,應(yīng)當(dāng)總是先檢查?***CompletedEventArgs.Error?和?***CompletedEventArgs.Cancelled?屬性,然后再訪問RunWorkerCompletedEventArgs.Result屬性。
?
BackgroundWorker組件
????System.ComponentModel命名空間的BackgroundWorker組件為我們提供了一個簡單的多線程應(yīng)用解決方案,它允許你在單獨的線程上運行耗時操作而不會導(dǎo)致用戶界面的阻塞。但是,要注意它同一時刻只能運行一個異步耗時操作(使用IsBusy屬性判定),并且不能跨AppDomain邊界進行封送處理(不能在多個AppDomain中執(zhí)行多線程操作)。
1.???BackgroundWorker組件
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | public?class?BackgroundWorker : Component { ????public?BackgroundWorker(); ? ????// 獲取一個值,指示應(yīng)用程序是否已請求取消后臺操作。 ????public?bool?CancellationPending { get; } ????// 獲取一個值,指示BackgroundWorker是否正在運行異步操作。 ????public?bool?IsBusy { get; } ????// 獲取或設(shè)置一個值,該值指示BackgroundWorker能否報告進度更新。 ????public?bool?WorkerReportsProgress { get; set; } ????// 獲取或設(shè)置一個值,該值指示BackgroundWorker是否支持異步取消。 ????public?bool?WorkerSupportsCancellation { get; set; } ? ????// 調(diào)用RunWorkerAsync() 時發(fā)生。 ????public?event?DoWorkEventHandlerDoWork; ????// 調(diào)用ReportProgress(System.Int32) 時發(fā)生。 ????public?event?ProgressChangedEventHandlerProgressChanged; ????// 當(dāng)后臺操作已完成、被取消或引發(fā)異常時發(fā)生。 ????public?event?RunWorkerCompletedEventHandlerRunWorkerCompleted; ? ????// 請求取消掛起的后臺操作。 ????public?void?CancelAsync(); ????// 引發(fā)ProgressChanged事件。percentProgress:范圍從 0% 到 100% ????public?void?ReportProgress(int?percentProgress); ????// userState:傳遞到RunWorkerAsync(System.Object) 的狀態(tài)對象。 ????public?void?ReportProgress(int?percentProgress, object?userState); ????// 開始執(zhí)行后臺操作。 ????public?void?RunWorkerAsync(); ????// 開始執(zhí)行后臺操作。argument:傳遞給DoWork事件的DoWorkEventArgs參數(shù)。 ????public?void?RunWorkerAsync(object?argument); } |
2.???相應(yīng)的EventArgs類
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | ///1)?? System.EventArgs基類 ????// System.EventArgs是包含事件數(shù)據(jù)的類的基類。 ????public?class?EventArgs ????{ ????????// 表示沒有事件數(shù)據(jù)的事件。 ????????public?static?readonly?EventArgs Empty; ????????public?EventArgs(); ????} ? ///2)?? DoWorkEventArgs類 ????// 為可取消的事件提供數(shù)據(jù)。 ????public?class?CancelEventArgs : EventArgs ????{ ????????public?CancelEventArgs(); ????????public?CancelEventArgs(bool?cancel); ????????// 獲取或設(shè)置指示是否應(yīng)取消事件的值。 ????????public?bool?Cancel { get; set; } ????} ????// 為DoWork事件處理程序提供數(shù)據(jù)。 ????public?class?DoWorkEventArgs : CancelEventArgs ????{ ????????public?DoWorkEventArgs(object?argument); ? ????????// 獲取表示異步操作參數(shù)的值。 ????????public?object?Argument { get; } ????????// 獲取或設(shè)置表示異步操作結(jié)果的值。 ????????public?object?Result { get; set; } ????} ? ///3)?? ProgressChangedEventArgs類 ????// 為ProgressChanged事件提供數(shù)據(jù)。 ????public?class?ProgressChangedEventArgs : EventArgs ????{ ????????public?ProgressChangedEventArgs(int?progressPercentage, object?userState); ? ????????// 獲取異步任務(wù)的進度百分比。 ????????public?int?ProgressPercentage { get; } ????????// 獲取唯一的用戶狀態(tài)。 ????????public?object?UserState { get; } ????} ? ///4)?? RunWorkerCompletedEventArgs類 ????// 為MethodNameCompleted事件提供數(shù)據(jù)。 ????public?class?AsyncCompletedEventArgs : EventArgs ????{ ????????public?AsyncCompletedEventArgs(); ????????public?AsyncCompletedEventArgs(Exception error, bool?cancelled, object?userState); ? ????????// 獲取一個值,該值指示異步操作是否已被取消。 ????????public?bool?Cancelled { get; } ????????// 獲取一個值,該值指示異步操作期間發(fā)生的錯誤。 ????????public?Exception Error { get; } ????????// 獲取異步任務(wù)的唯一標識符。 ????????public?object?UserState { get; } ? ????????// 訪問 AsyncCompletedEventArgs 及其派生類的屬性前調(diào)用此方法 ????????protected?void?RaiseExceptionIfNecessary() ????????{ ????????????if?(this.Error != null) ????????????{ ????????????????throw?new?TargetInvocationException(……); ????????????} ????????????if?(this.Cancelled) ????????????{ ????????????????throw?new?InvalidOperationException(……); ????????????} ????????} ????} ????public?class?RunWorkerCompletedEventArgs : AsyncCompletedEventArgs ????{ ????????public?RunWorkerCompletedEventArgs(object?result, Exception error, bool?cancelled); ? ????????// 獲取表示異步操作結(jié)果的值。 ????????public?object?Result { get; } ????????// 獲取表示用戶狀態(tài)的值。 ????????public?object?UserState { get; } ????} |
3.???BackgroundWorker示例
示例代碼中包含了BackgroundWorker源代碼及對應(yīng)的使用示例,這里不粘貼代碼了,會導(dǎo)致篇幅更大。來個示例截圖吧:
?
示例分析:
1)???首先我們?yōu)锽ackgroundWorker組件注冊DoWork(異步操作)、ProgressChanged(進度報告)?和RunWorkCompleted(完成通知)事件;
2)???設(shè)置WorkerSupportsCancellation和WorkerReportsProgress屬性為true,以聲明組件支持取消操作和進度報告;
3)???使用RunWorkerAsync()?開啟異步操作,通過IsBusy屬性判斷是否已經(jīng)有異步任務(wù)在執(zhí)行;
4)???使用CancelAsync()?方法取消異步操作,但要注意:
a)???它僅僅是將BackgroudWorker.CancellationPending屬性設(shè)置為true。需要在具體DoWork事件中不斷檢查BackgroudWorker.CancellationPending來設(shè)置DoWorkEventArgs的Cancel屬性。
b)???DoWork事件處理程序中的代碼有可能在發(fā)出取消請求時完成其工作,輪詢循環(huán)可能會錯過設(shè)置為?true?的CancellationPending屬性。在這種情況下,即使發(fā)出了取消請求,RunWorkerCompleted事件處理程序中RunWorkerCompletedEventArgs的?Cancelled?標志也不會設(shè)置為?true。這種情況被稱作爭用狀態(tài)。(可以通過直接監(jiān)控組件的CancellationPending屬性,來做判斷)
5)???確保在DoWork事件處理程序中不操作任何用戶界面對象。而應(yīng)該通過ProgressChanged和RunWorkerCompleted事件與用戶界面進行通信。
因為RunWorkerAsync()?是通過委托的BeginInvoke()?引發(fā)的DoWork事件,即DoWork事件的執(zhí)行線程已不是創(chuàng)建控件的線程(我在《異步編程:異步編程模型 (APM)》中介紹了幾種夸線程訪問控件的方式)。而ProgressChanged和RunWorkerCompleted事件是通過幫助器類AsyncOperation的?Post()?方法使其調(diào)用發(fā)生在合適的“線程或上下文”中。
?
自定義基于事件的異步組件
????剛才我們介紹了BackgroundWorker組件,但是這個組件在一個時刻只能開啟一個異步操作,那如果我們要想同時支持多個異步操作、進度報告、增量結(jié)果、取消和返回結(jié)果值或異常信息該怎么辦呢?對的,我們可以為自己定義一個基于事件的異步組件。
????我直接引用MSDN上的一則計算質(zhì)數(shù)的異步組件示例,請從我提供的示例代碼中獲取。
質(zhì)數(shù)算法:埃拉托色尼篩法
eg:判斷n是否為質(zhì)數(shù)
1、1和0既非素數(shù)也非合數(shù);
2、將2和3加入質(zhì)數(shù)集合primes;從n=5開始,通過?n+=2?來跳過所有偶數(shù);
3、循環(huán)集合primes中的質(zhì)數(shù)并將其做為n的因子,能整除的為合數(shù);
4、若不能整除,則繼續(xù)循步驟3直到“因子的平方>n”,即可判斷n為質(zhì)數(shù),并將其加入到集合primes。
?
來個示例截圖吧:
?
示例分析:(組件名:PrimeNumberCalculator)
1.???首先我們?yōu)镻rimeNumberCalculator組件注冊ProgressChanged(進度報告)?和CalculatePrimeCompleted(完成通知)事件;
2.???使用CalculatePrimeAsync(intnumberToTest, object taskId)開啟異步任務(wù),注意我們需要傳遞一個唯一標識Guid taskId = Guid.NewGuid();用于標識取消的操作,并傳遞給CompletedEventArgs和ProgressChangedEventArgs參數(shù)的UserState屬性來標識當(dāng)前引發(fā)進度或完成事件的特定異步任務(wù);
3.???取消操作CancelAsync(object taskId),只是將taskId對應(yīng)的AsyncOperation實例移除內(nèi)部任務(wù)集合,耗時操作通過判斷taskId是否存在于集合來判斷其是否被取消;
?
?
此文到此結(jié)束,通過此博文我們認識到:
1)???基于事件的異步編程是通過AsyncOperationManager類和AsyncOperation類兩個幫助器類確保你的異步操作支持在各種應(yīng)用程序模型(包括?ASP.NET、控制臺應(yīng)用程序和?Windows?窗體應(yīng)用程序)的適當(dāng)“線程或上下文”調(diào)用訪問控件;
2)???BackgroundWorker組件構(gòu)建、使用和缺點。
3)???展現(xiàn)如何構(gòu)建一個基于事件的異步組件,并且支持多個異步操作的并行運行
?
感謝大家的觀賞,如本文對你有幫助還請多幫推薦支持下……
?
?
?
參考:MSDN
?????????????書籍:《CLR via C#(第三版)》
總結(jié)
以上是生活随笔為你收集整理的【转】1.7异步编程:基于事件的异步编程模式(EAP)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 天然橡胶是白的 为何做成轮胎变成黑?
- 下一篇: 曝《海王2》将删除“海后”戏份:重新选角