實作事件架構非同步模式的最佳作法
事件架構非同步模式提供有效率的方式,讓您運用熟悉的事件和委派 (Delegate) 語意,公開類別中的非同步行為。 若要實作事件架構非同步模式,您需要遵循一些特定的行為需求。 下列章節會描述在實作遵循事件架構非同步模式的類別時,您所需要考慮的需求和方針。
如需概觀說明,請參閱實作事件架構非同步模式。
下列清單會顯示本主題中討論的最佳作法:
需要的行為保證
完成
完成的事件和 EventArgs
同時執行作業
存取結果
進度報告
IsBusy 實作
取消
錯誤和例外狀況
執行緒和內容
方針
需要的行為保證
如果要實作事件架構非同步模式,您必須提供數種保證,確定您的類別將有適當的行為,而且類別的用戶端也能依靠這種行為。
完成
在成功完成、發生錯誤或取消作業時,一定都要叫用 (Invoke) MethodNameCompleted 事件處理常式。 應用程式應該絕不會發生維持閒置和永遠無法完成的狀況。 這項規則的唯一例外,是在非同步作業 (Asynchronous Operation) 本身設計為永遠無法完成的時候。
完成的事件和 EventArgs
對於每個個別 MethodNameAsync 方法,套用下列設計需求:
在與此方法相同的類別上定義 MethodNameCompleted 事件。
針對從 AsyncCompletedEventArgs 類別衍生的 MethodNameCompleted 事件定義 EventArgs 類別和伴隨的委派。 預設類別名稱的格式應該為 MethodNameCompletedEventArgs。
確定 EventArgs 類別是 MethodName 方法的傳回值所特有。 當您使用 EventArgs 類別時,應該不要讓開發人員需要轉型 (Cast) 結果。
下列程式碼範例會個別示範這項設計需求的良好和不良實作。
[C#]
// Good design
private void Form1_MethodNameCompleted(object sender, xxxCompletedEventArgs e)
{
DemoType result = e.Result;
}
// Bad design
private void Form1_MethodNameCompleted(object sender, MethodNameCompletedEventArgs e)
{
DemoType result = (DemoType)(e.Result);
}
請勿對傳回 void 的傳回方法定義 EventArgs 類別, 而是使用 AsyncCompletedEventArgs 類別的執行個體。
務必一定要引發 MethodNameCompleted 事件。 此事件應該在成功完成、發生錯誤或取消時引發。 應用程式應該絕不會發生維持閒置和永遠無法完成的狀況。
務必要攔截發生在非同步作業中的任何例外狀況,並且將攔截到的例外狀況指派給 Error 屬性。
如果完成工作時發生錯誤,就應該會無法存取結果。 當 Error 屬性不是 null 時,務必確定存取 EventArgs 結構中的任何屬性都會引發例外狀況。 使用 RaiseExceptionIfNecessary 方法來執行這項驗證。
將逾時模型化為錯誤。 發生逾時的時候,引發 MethodNameCompleted 事件並且將 TimeoutException 指派給 Error 屬性。
如果您的類別支援多個並行引動過程,務必確定 MethodNameCompleted 事件包含適當的 userSuppliedState 物件。
確定 MethodNameCompleted 事件會在適當的執行緒上,以及應用程式生命週期中的適當時間引發。 如需詳細資訊,請參閱<執行緒和內容>一節。
同時執行作業
如果您的類別支援多個並行引動過程,請讓開發人員定義 MethodNameAsync 多載 (此多載使用物件值狀態參數,或是稱為 userSuppliedState 的工作 ID),藉此分別追蹤每個引動過程。 這個參數應該永遠都是 MethodNameAsync 方法簽章 (Signature) 中的最後一個參數。
如果您的類別定義使用物件值狀態參數或工作 ID 的 MethodNameAsync 多載,請務必使用此工作 ID 來追蹤作業的存留期 (Lifetime),並將它帶回完成處理常式。 一些 Helper 類別都能對此提供協助。 如需並行管理的詳細資訊,請參閱逐步解說:實作支援事件架構非同步模式的元件。
如果您的類別定義不含狀態參數的 MethodNameAsync 方法,而且不支援多個並行引動過程,請確定之前的 MethodNameAsync 引動過程完成之前,叫用 MethodNameAsync 的任何嘗試動作都會引發 InvalidOperationException。
一般而言,如果叫用 MethodNameAsync 方法多次而不使用 userSuppliedState 參數,就會形成多次的未完成作業,這時請不要引發例外狀況。 當您的類別明確地無法處理這種情況,而且開發人員應該能夠處理這些無法區分的多重回呼 (Callback) 時,您就能夠引發例外狀況。
存取結果
如果在執行非同步作業時發生錯誤,就應該會無法存取結果。 確定當 Error 不為 null 時,存取 AsyncCompletedEventArgs 中的任何屬性,都會引發 Error 所參考的例外狀況。 為此,AsyncCompletedEventArgs 類別提供了 RaiseExceptionIfNecessary 方法。
確定存取結果的任何嘗試,都會引發表示作業已經取消的 InvalidOperationException。 使用 AsyncCompletedEventArgs.RaiseExceptionIfNecessary 方法來執行這項驗證。
進度報告
在可能時,支援進度報告。 這讓開發人員能在使用您的類別時,對應用程式使用者提供更好的體驗。
如果您實作 ProgressChanged/MethodNameProgressChanged 事件,請確定在特定非同步作業的 MethodNameCompleted 事件已經引發之後,不會對該作業引發這種事件。
如果標準的 ProgressChangedEventArgs 已經填入 (Populate),請確定 ProgressPercentage 一定都能解譯為百分比。 這種百分比不需要非常精確,不過必須表示出某個百分比。 如果您的進度報告度量資訊 (Metric) 必須不是百分比,請從 ProgressChangedEventArgs 類別衍生一個類別,並將 ProgressPercentage 保持為 0。 請避免使用不為百分比的報告度量資料。
確定 ProgressChanged 事件會在適當的執行緒上,以及應用程式生命週期中的適當時間引發。 如需詳細資訊,請參閱<執行緒和內容>一節。
IsBusy 實作
如果您的類別支援多個並行引動過程,就不要公開 IsBusy 屬性。 例如,XML Web Service Proxy 不會公開 IsBusy 屬性,因為這些 Proxy 支援非同步方法的多重並行引動過程。
IsBusy 屬性在呼叫 MethodNameAsync 方法之後,以及引發 MethodNameCompleted 事件之前,應該會傳回 true。 否則會傳回 false。 BackgroundWorker 和 WebClient 元件都是公開 IsBusy 屬性之類別的範例。
取消
在可能時,支援取消。 這讓開發人員能在使用您的類別時,對應用程式使用者提供更好的體驗。
在取消的情況下,請設定 AsyncCompletedEventArgs 物件中的 Cancelled 旗標。
確定存取結果的任何嘗試,都會引發表示作業已經取消的 InvalidOperationException。 使用 AsyncCompletedEventArgs.RaiseExceptionIfNecessary 方法來執行這項驗證。
確定對於取消方法的呼叫永遠都會成功傳回,而且絕對不會引發例外狀況。 一般而言,不會在任何時候通知用戶端是否真的能夠取消作業,而且也不會通知用戶端先前發出的取消是否已經成功。 然而,由於應用程式參與完成狀態,所以在取消成功時必定都會通知應用程式。
在取消作業時引發 MethodNameCompleted 事件。
錯誤和例外狀況
- 攔截發生在非同步作業中的任何例外狀況,並將 AsyncCompletedEventArgs.Error 屬性的值設定為該例外狀況。
執行緒和內容
如需進行類別的正確作業,在適當的執行緒,或是特定應用程式模型 (包括 ASP.NET 和 Windows Form 應用程式) 的內容,叫用應用程式的事件處理常式,是相當重要的。 以下兩個重要 Helper 類別:AsyncOperation 和 AsyncOperationManager,都可確定您的非同步類別在任何應用程式模型下都具有正確行為。
AsyncOperationManager 提供一個方法 CreateOperation,此方法會傳回 AsyncOperation。 您的 MethodNameAsync 方法會呼叫 CreateOperation,而您的類別會使用傳回的 AsyncOperation 來追蹤非同步工作的存留期。
若要向用戶端報告進度、累加結果及完成,請呼叫 AsyncOperation 上的 Post 和 OperationCompleted。 AsyncOperation會負責將用戶端事件處理常式的呼叫,封送處理至適當的執行緒或內容。
注意事項 |
---|
如果要明確違背應用程式模型的原則,不過依然要受益於使用事件架構非同步模式的其他優點,您可以規避這些規則。例如,您可能要讓 Windows Form 中的類別作業,成為無限制執行緒的作業。只要開發人員了解隱含的限制,您就可以建立無限制執行緒的類別。主控台應用程式 (Console Application) 不會同步執行 Post 呼叫。這可能會造成 ProgressChanged 事件不按順序引發。如果您想要以序列化方式執行 Post 呼叫,請實作及安裝 System.Threading.SynchronizationContext 類別。 |
如需使用 AsyncOperation 和 AsyncOperationManager 啟用非同步作業的詳細資訊,請參閱逐步解說:實作支援事件架構非同步模式的元件。
方針
理想上,每個方法引動過程都應該獨立於其他引動過程。 您應該避免與共用資源形成連接引動過程。 如果要在引動過程之間共用資源,就需要在您的實作中提供適當的同步處理機制。
需要用戶端實作同步處理的設計是令人沮喪的。 例如,您可能有一個非同步方法,接受一個全域靜態物件做為參數。這種方法的多個並行引動過程,就有可能造成資料損毀或死結 (Deadlock)。
如果實作具有多重引動過程多載 (簽章中的 userState) 的方法,您的類別就會需要管理使用者狀態 (或是工作 ID) 及其對應擱置中作業的集合。 對於這個集合應該要以 lock 區域保護,因為各種引動過程都會加入和移除集合中的 userState 物件。
在可行和適當的地方,請考慮重複使用 CompletedEventArgs 類別。 在此情況下,由於指定的委派和 EventArgs 型別不會繫結至單一方法,所以命名將不會與方法名稱一致。 然而,強迫開發人員轉型從 EventArgs 上屬性擷取的值,是絕對不能接受的。
如果您正在撰寫衍生自 Component 的類別,請勿實作及安裝您自己的 SynchronizationContext 類別。 應用程式模型 (而不是元件) 會控制使用的 SynchronizationContext。
當您使用任何種類的多執行緒時,都有可能將自己暴露在非常危險和複雜的錯誤之下 在實作使用多執行緒的任何方案之前,請參閱 Managed 執行緒處理的最佳實施方針。