共用方式為


實作事件驅動非同步模式的最佳做法

基於事件的非同步模式為您提供在類別中公開非同步行為的有效方法,並具備熟悉的事件和委派語意。 若要實作事件架構異步模式,您必須遵循一些特定的行為需求。 下列各節說明當您實作遵循事件架構異步模式的類別時,應考慮的需求和指導方針。

如需概觀,請參閱 實作事件架構異步模式

必要的行為保證

如果您實作事件導向非同步模式,您必須提供一些保證,以確保您的類別行為正常,而其用戶端可以依賴這類行為。

完成

當您成功完成、錯誤或取消時,請一律叫用 MethodNameCompleted 事件處理程式。 應用程式不應該遇到它們保持閑置且永遠不會完成的情況。 此規則的一個例外是,如果異步操作本身被設計成永遠不會完成。

已完成的事件和 EventArgs

針對每個個別的 MethodNameAsync 方法,請套用下列設計需求:

  • 在與方法相同的類別上定義 MethodNameCompleted 事件。

  • 為衍生自EventArgs類別的MethodNameCompleted事件定義一個類別和一個相應的委派。 默認類別名稱的格式應該是 MethodNameCompletedEventArgs

  • 請確保 EventArgs 類別是針對 MethodName 方法的回傳值而設的。 當您使用 類別 EventArgs 時,不應該要求開發人員轉換結果。

    下列程式代碼範例分別顯示此設計需求的良好和不良實作。

// 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);
}
  • 請勿定義傳 EventArgs 回 方法的類別,以傳回 void。 請改用 AsyncCompletedEventArgs 類別的實例。

  • 確保您始終引發MethodNameCompleted事件。 成功完成、發生錯誤或取消時,應觸發此事件。 應用程式不應該遇到它們保持閑置且永遠不會完成的情況。

  • 請確定您攔截異步作中發生的任何例外狀況,並將攔截的例外狀況指派給 Error 屬性。

  • 如果完成工作時發生錯誤,則不應該存取結果。 Error當 屬性不是 null時,請確定存取 結構中的任何EventArgs屬性會引發例外狀況。 RaiseExceptionIfNecessary使用方法來執行此驗證。

  • 將逾時模型化為錯誤。 發生逾時時,引發 MethodNameCompleted 事件,並指派 TimeoutExceptionError 屬性。

  • 如果您的類別支援多個並行調用,請確定 MethodNameCompleted 事件包含適當的 userSuppliedState 物件。

  • 確定 MethodNameCompleted 事件會在適當的線程上引發,並在應用程式生命週期中的適當時間引發。 如需詳細資訊,請參閱線程和內容一節。

同時執行作業

  • 如果您的類別支援多個並行調用,請藉由定義採用物件值狀態參數或工作標識碼的 MethodNameuserSuppliedState 多載,讓開發人員能夠個別追蹤每個調用。 此參數一律應該是 MethodNameAsync 方法簽章中的最後一個參數。

  • 如果您的類別定義 MethodNameAsync 多載,且該多載採用物件值的狀態參數或工作識別碼,請務必使用該工作識別碼來追蹤操作的存續時間,並務必將其傳回至完成處理器。 有輔助類別可提供支援。 如需並行管理的詳細資訊,請參閱 如何:實作支援事件架構異步模式的元件

  • 如果您的類別在沒有 state 參數的情況下定義了 MethodNameAsync 方法,且不支援多個並行調用,請確保在先前的 MethodNameAsync 調用完成之前,任何嘗試調用 MethodNameAsync 都會引發一個錯誤InvalidOperationException

  • 一般而言,如果未帶參數呼叫MethodNameAsync方法多次,導致有多個未完成的作業,不應引發例外。 當您的類別明確無法處理那種情況時,您可以拋出例外,但假設開發人員能夠處理這些難以區分的多個回呼。

存取結果

進度報告

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

  • 如果您實作 ProgressChangedMethodNameProgressChanged 事件,請確定在該作業的 MethodNameCompleted 事件引發之後,特定異步作不會引發這類事件。

  • 如果填入標準 ProgressChangedEventArgs ,請確定 ProgressPercentage 一律可以解譯為百分比。 百分比不需要準確,但應該代表百分比。 如果您的進度報告計量必須使用百分比之外的其他標準,請從 ProgressChangedEventArgs 類別派生一個類別,並將 ProgressPercentage 保持為 0。 避免使用百分比以外的報告計量。

  • 請確定 ProgressChanged 事件是在適當的線程上引發,並在應用程式生命週期中的適當時間引發。 如需詳細資訊,請參閱線程和內容一節。

IsBusy 的實作

  • 如果您的類別支援多個並行調用,請勿公開 IsBusy 屬性。 例如,XML Web 服務 Proxy 不會公開 IsBusy 屬性,因為它們支援異步方法的多個並行調用。

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

取消

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

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

  • 請確定任何嘗試存取結果時, InvalidOperationException 都引發 指出作業已取消。 AsyncCompletedEventArgs.RaiseExceptionIfNecessary使用方法來執行此驗證。

  • 請確定對取消方法的呼叫一律會成功傳回,而且絕不會引發例外狀況。 一般而言,用戶端不會被通知在任何時間某個作業是否確實可取消,也不知道先前發出的取消是否成功。 不過,應用程式在取消成功時一律會接收到通知,因為應用程式會參與完成狀態的處理。

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

錯誤和例外狀況

線程和內容

為了確保您的類別正常運行,對於指定的應用程式模型,包括 ASP.NET 和 Windows Forms 應用程式,必須在正確的線程或上下文中執行用戶端的事件處理程序。 提供兩個重要的協助程式類別,以確保您的異步類別在任何應用程式模型中都正確運作: AsyncOperationAsyncOperationManager

AsyncOperationManager 提供一個 方法, CreateOperation這個方法會傳 AsyncOperation回 。 您的 MethodNameAsync方法會呼叫CreateOperation,而您的類別使用傳回的AsyncOperation結果來追蹤異步任務的存留期。

若要向用戶端報告進度、累積性結果和完成,請在Post上呼叫OperationCompletedAsyncOperation方法。 AsyncOperation 負責將用戶端事件處理器的呼叫調度到適當的線程或內容。

備註

如果您明確想要違反應用程式模型的原則,但仍受益於使用事件架構異步模式的其他優點,您可以規避這些規則。 例如,您可能想要在 Windows Forms 中作業的類別成為自由線程。 只要開發人員瞭解隱含的限制,您就可以建立免費線程類別。 主控台應用程式不會同步執行Post的呼叫。 這可能會導致 ProgressChanged 事件錯誤順序地引發。 如果您希望執行 Post 呼叫時採用串行方式,請實作並安裝 System.Threading.SynchronizationContext 類別。

如需有關使用AsyncOperationAsyncOperationManager來啟用您的非同步作業的詳細資訊,請參閱如何:實作支援以事件為基礎的非同步模式的元件

指導方針

  • 在理想情況下,每個方法調用都應該與其他方法無關。 您應該避免將調用與共用資源結合。 如果要在調用之間共享資源,您必須在實作中提供適當的同步處理機制。

  • 不建議使用需要客戶端實作同步處理的設計。 例如,您可以有異步方法接收全域靜態物件做為參數;這類方法的多個並行調用可能會導致數據損毀或死結。

  • 如果您實作具有複數調用重載(userState 在簽章中)的方法,您的類別將需要管理用戶狀態和工作標識碼的集合,以及其對應的擱置作業。 此集合應受到 lock 區域保護,因為集合中的各種調用會新增和移除 userState 物件。

  • 請考慮在可行且適當的情況下重複使用 CompletedEventArgs 類別。 在此情況下,命名與方法名稱不一致,因為指定的委派和 EventArgs 類型不會系結至單一方法。 不過,強制開發人員轉換從 EventArgs 上的一個屬性擷取的值是絕對不可接受的。

  • 如果您要撰寫衍生自 Component的類別,請勿實作並安裝您自己的 SynchronizationContext 類別。 應用程式模型而非元件來控制所使用的 SynchronizationContext

  • 當您使用任何類型的多線程時,可能會讓自己暴露在非常嚴重且複雜的 Bug 中。 實作任何使用多線程的解決方案之前,請參閱 受控線程最佳做法

另請參閱