共用方式為


實作事件架構非同步模式的最佳作法

事件架構非同步模式提供有效率的方式,讓您運用熟悉的事件和委派 (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) 時,您就能夠引發例外狀況。

存取結果

進度報告

  • 在可能時,支援進度報告。 這讓開發人員能在使用您的類別時,對應用程式使用者提供更好的體驗。

  • 如果您實作 ProgressChanged/MethodNameProgressChanged 事件,請確定在特定非同步作業的 MethodNameCompleted 事件已經引發之後,不會對該作業引發這種事件。

  • 如果標準的 ProgressChangedEventArgs 已經填入 (Populate),請確定 ProgressPercentage 一定都能解譯為百分比。 這種百分比不需要非常精確,不過必須表示出某個百分比。 如果您的進度報告度量資訊 (Metric) 必須不是百分比,請從 ProgressChangedEventArgs 類別衍生一個類別,並將 ProgressPercentage 保持為 0。 請避免使用不為百分比的報告度量資料。

  • 確定 ProgressChanged 事件會在適當的執行緒上,以及應用程式生命週期中的適當時間引發。 如需詳細資訊,請參閱<執行緒和內容>一節。

IsBusy 實作

  • 如果您的類別支援多個並行引動過程,就不要公開 IsBusy 屬性。 例如,XML Web Service Proxy 不會公開 IsBusy 屬性,因為這些 Proxy 支援非同步方法的多重並行引動過程。

  • IsBusy 屬性在呼叫 MethodNameAsync 方法之後,以及引發 MethodNameCompleted 事件之前,應該會傳回 true。 否則會傳回 false。 BackgroundWorkerWebClient 元件都是公開 IsBusy 屬性之類別的範例。

取消

  • 在可能時,支援取消。 這讓開發人員能在使用您的類別時,對應用程式使用者提供更好的體驗。

  • 在取消的情況下,請設定 AsyncCompletedEventArgs 物件中的 Cancelled 旗標。

  • 確定存取結果的任何嘗試,都會引發表示作業已經取消的 InvalidOperationException。 使用 AsyncCompletedEventArgs.RaiseExceptionIfNecessary 方法來執行這項驗證。

  • 確定對於取消方法的呼叫永遠都會成功傳回,而且絕對不會引發例外狀況。 一般而言,不會在任何時候通知用戶端是否真的能夠取消作業,而且也不會通知用戶端先前發出的取消是否已經成功。 然而,由於應用程式參與完成狀態,所以在取消成功時必定都會通知應用程式。

  • 在取消作業時引發 MethodNameCompleted 事件。

錯誤和例外狀況

執行緒和內容

如需進行類別的正確作業,在適當的執行緒,或是特定應用程式模型 (包括 ASP.NET 和 Windows Form 應用程式) 的內容,叫用應用程式的事件處理常式,是相當重要的。 以下兩個重要 Helper 類別:AsyncOperationAsyncOperationManager,都可確定您的非同步類別在任何應用程式模型下都具有正確行為。

AsyncOperationManager 提供一個方法 CreateOperation,此方法會傳回 AsyncOperation。 您的 MethodNameAsync 方法會呼叫 CreateOperation,而您的類別會使用傳回的 AsyncOperation 來追蹤非同步工作的存留期。

若要向用戶端報告進度、累加結果及完成,請呼叫 AsyncOperation 上的 PostOperationCompletedAsyncOperation會負責將用戶端事件處理常式的呼叫,封送處理至適當的執行緒或內容。

注意事項注意事項

如果要明確違背應用程式模型的原則,不過依然要受益於使用事件架構非同步模式的其他優點,您可以規避這些規則。例如,您可能要讓 Windows Form 中的類別作業,成為無限制執行緒的作業。只要開發人員了解隱含的限制,您就可以建立無限制執行緒的類別。主控台應用程式 (Console Application) 不會同步執行 Post 呼叫。這可能會造成 ProgressChanged 事件不按順序引發。如果您想要以序列化方式執行 Post 呼叫,請實作及安裝 System.Threading.SynchronizationContext 類別。

如需使用 AsyncOperationAsyncOperationManager 啟用非同步作業的詳細資訊,請參閱逐步解說:實作支援事件架構非同步模式的元件

方針

  • 理想上,每個方法引動過程都應該獨立於其他引動過程。 您應該避免與共用資源形成連接引動過程。 如果要在引動過程之間共用資源,就需要在您的實作中提供適當的同步處理機制。

  • 需要用戶端實作同步處理的設計是令人沮喪的。 例如,您可能有一個非同步方法,接受一個全域靜態物件做為參數。這種方法的多個並行引動過程,就有可能造成資料損毀或死結 (Deadlock)。

  • 如果實作具有多重引動過程多載 (簽章中的 userState) 的方法,您的類別就會需要管理使用者狀態 (或是工作 ID) 及其對應擱置中作業的集合。 對於這個集合應該要以 lock 區域保護,因為各種引動過程都會加入和移除集合中的 userState 物件。

  • 在可行和適當的地方,請考慮重複使用 CompletedEventArgs 類別。 在此情況下,由於指定的委派和 EventArgs 型別不會繫結至單一方法,所以命名將不會與方法名稱一致。 然而,強迫開發人員轉型從 EventArgs 上屬性擷取的值,是絕對不能接受的。

  • 如果您正在撰寫衍生自 Component 的類別,請勿實作及安裝您自己的 SynchronizationContext 類別。 應用程式模型 (而不是元件) 會控制使用的 SynchronizationContext

  • 當您使用任何種類的多執行緒時,都有可能將自己暴露在非常危險和複雜的錯誤之下 在實作使用多執行緒的任何方案之前,請參閱 Managed 執行緒處理的最佳實施方針

請參閱

工作

HOW TO:使用支援事件架構非同步模式的元件

逐步解說:實作支援事件架構非同步模式的元件

參考

AsyncOperation

AsyncOperationManager

AsyncCompletedEventArgs

ProgressChangedEventArgs

BackgroundWorker

概念

實作事件架構非同步模式

決定何時實作事件架構非同步模式

實作事件架構非同步模式的最佳作法

其他資源

使用事件架構非同步模式設計多執行緒程式