次の方法で共有


イベント ベースの非同期パターンを実装するためのベスト プラクティス

イベント ベースの非同期パターンでは、使い慣れたイベントとデリゲートのセマンティクスを使用して、クラスで非同期動作を公開する効果的な方法が提供されます。 イベント ベースの非同期パターンを実装するには、いくつかの特定の動作要件に従う必要があります。 以降のセクションでは、イベント ベースの非同期パターンに従うクラスを実装する場合に考慮する必要がある要件とガイドラインについて説明します。

概要については、「 イベント ベースの非同期パターンの実装」を参照してください。

必要な動作の保証

イベントベースの非同期パターンを実装する場合は、クラスが適切に動作し、クラスのクライアントがそのような動作に依存できることを保証するために、いくつかの保証を提供する必要があります。

完了

完了、エラー、または取り消しが成功した場合は、常に MethodNameCompleted イベント ハンドラーを呼び出します。 アプリケーションがアイドル状態のままで、完了が発生しない状況に遭遇することは決してありません。 この規則の 1 つの例外は、非同期操作自体が完了しないように設計されている場合です。

完了したイベントと EventArgs

個別の MethodNameAsync メソッドごとに、次の設計要件を適用します。

  • メソッドと同じクラスで MethodNameCompleted イベントを定義します。

  • AsyncCompletedEventArgs クラスから派生する MethodNameCompleted イベントのEventArgs クラスとそれに付随するデリゲートを定義します。 既定のクラス名は 、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);
}
  • voidを返すメソッドを返すEventArgs クラスを定義しないでください。 代わりに、 AsyncCompletedEventArgs クラスのインスタンスを使用します。

  • 必ず MethodNameCompleted イベントを発生させます。 このイベントは、正常に完了した場合、エラーが発生した場合、または取り消し時に発生する必要があります。 アプリケーションがアイドル状態のままで、完了が発生しない状況に遭遇することは決してありません。

  • 非同期操作で発生する例外をキャッチし、キャッチされた例外を Error プロパティに割り当てます。

  • タスクの完了中にエラーが発生した場合は、結果にアクセスできません。 Error プロパティがnullされていない場合は、EventArgs構造体内の任意のプロパティにアクセスすると例外が発生することを確認します。 この検証を実行するには、 RaiseExceptionIfNecessary メソッドを使用します。

  • タイムアウトをエラーとしてモデル化します。 タイムアウトが発生したら、MethodNameCompleted イベントを発生させ、Error プロパティにTimeoutExceptionを割り当てます。

  • クラスで複数の同時呼び出しがサポートされている場合は、 MethodNameCompleted イベントに適切な userSuppliedState オブジェクトが含まれていることを確認します。

  • MethodNameCompleted イベントが適切なスレッドで、アプリケーションライフサイクルの適切なタイミングで発生していることを確認します。 詳細については、「スレッドとコンテキスト」セクションを参照してください。

操作を同時に実行する

  • クラスで複数の同時呼び出しがサポートされている場合は、オブジェクト値の状態パラメーターを受け取る MethodNameAsync オーバーロード( userSuppliedState呼び出されたタスク ID) を定義することで、開発者が各呼び出しを個別に追跡できるようにします。 このパラメーターは、 常に MethodNameAsync メソッドのシグネチャの最後のパラメーターである必要があります。

  • オブジェクト値の状態パラメーターまたはタスク ID を受け取る MethodNameAsync オーバーロードがクラスで定義されている場合は、そのタスク ID を使用して操作の有効期間を追跡し、完了ハンドラーに戻してください。 支援できるヘルパー クラスがあります。 コンカレンシー管理の詳細については、「 方法: イベント ベースの非同期パターンをサポートするコンポーネントを実装する」を参照してください。

  • クラスで状態パラメーターを指定せずに MethodNameAsync メソッドを定義し、複数の同時呼び出しをサポートしていない場合は、前の MethodNameAsync 呼び出しが完了する前に MethodNameAsync を呼び出そうとすると、 InvalidOperationExceptionが発生することを確認します。

  • 一般に、userSuppliedState パラメーターのない MethodNameAsync メソッドが複数回呼び出され、未処理の操作が複数ある場合は、例外を発生させないでください。 クラスがその状況を明示的に処理できない場合は例外を発生させることができますが、開発者はこれらの複数の区別できないコールバックを処理できることを前提としています

結果へのアクセス

進行状況レポート

  • 可能であれば、進行状況レポートをサポートします。 これにより、開発者はクラスを使用するときに、より優れたアプリケーション ユーザー エクスペリエンスを提供できます。

  • ProgressChanged イベントまたは MethodNameProgressChanged イベントを実装する場合は、その操作の MethodNameCompleted イベントが発生した後に、特定の非同期操作に対してこのようなイベントが発生していないことを確認します。

  • 標準 ProgressChangedEventArgs が設定されている場合は、 ProgressPercentage を常にパーセンテージとして解釈できることを確認します。 パーセンテージは正確である必要はありませんが、パーセンテージを表す必要があります。 進行状況レポート メトリックがパーセンテージ以外の値である必要がある場合は、 ProgressChangedEventArgs クラスからクラスを派生させ、 ProgressPercentage を 0 のままにします。 パーセンテージ以外のレポート メトリックは使用しないでください。

  • ProgressChanged イベントが適切なスレッドで発生し、アプリケーションのライフサイクルの適切なタイミングで発生していることを確認します。 詳細については、「スレッドとコンテキスト」セクションを参照してください。

IsBusy の実装

  • クラスが複数の同時呼び出しをサポートしている場合は、 IsBusy プロパティを公開しないでください。 たとえば、XML Web サービス プロキシは、非同期メソッドの複数の同時呼び出しをサポートするため、 IsBusy プロパティを公開しません。

  • IsBusy プロパティは、MethodNameAsync メソッドが呼び出された後、および MethodNameCompleted イベントが発生する前に、trueを返す必要があります。 それ以外の場合は、 falseを返す必要があります。 BackgroundWorkerコンポーネントとWebClient コンポーネントは、IsBusy プロパティを公開するクラスの例です。

キャンセル

  • 可能であれば、キャンセルをサポートします。 これにより、開発者はクラスを使用するときに、より優れたアプリケーション ユーザー エクスペリエンスを提供できます。

  • 取り消しの場合は、AsyncCompletedEventArgs オブジェクトにCancelled フラグを設定します。

  • 結果にアクセスしようとすると、操作が取り消されたことを示す InvalidOperationException が発生することを確認します。 この検証を実行するには、 AsyncCompletedEventArgs.RaiseExceptionIfNecessary メソッドを使用します。

  • キャンセル メソッドの呼び出しが常に正常に返されるようにし、例外を発生させないようにします。 一般に、クライアントは、操作が特定の時点で本当に取り消し可能かどうかについては通知されず、以前に発行されたキャンセルが成功したかどうかについては通知されません。 ただし、アプリケーションは完了状態に参加するため、取り消しに成功すると、常に通知されます。

  • 操作が取り消されたときに MethodNameCompleted イベントを発生させます。

エラーと例外

  • 非同期操作で発生した例外をキャッチし、 AsyncCompletedEventArgs.Error プロパティの値をその例外に設定します。

スレッドとコンテキスト

クラスを正しく操作するには、クライアントのイベント ハンドラーが、ASP.NET アプリケーションや Windows フォーム アプリケーションなど、特定のアプリケーション モデルの適切なスレッドまたはコンテキストで呼び出されていることが重要です。 AsyncOperationAsyncOperationManagerの 2 つの重要なヘルパー クラスが用意されており、任意のアプリケーション モデルで非同期クラスが正しく動作します。

AsyncOperationManagerには、AsyncOperationを返す 1 つのメソッド (CreateOperation) が用意されています。 MethodNameAsync メソッドはCreateOperationを呼び出し、クラスは返されたAsyncOperationを使用して非同期タスクの有効期間を追跡します。

進行状況、増分結果、完了をクライアントに報告するには、AsyncOperationPostメソッドとOperationCompleted メソッドを呼び出します。 AsyncOperation は、クライアントのイベント ハンドラーへの呼び出しを適切なスレッドまたはコンテキストにマーシャリングする役割を担います。

アプリケーション モデルのポリシーに明示的に反する必要があるが、イベント ベースの非同期パターンを使用する他の利点の恩恵を受ける場合は、これらの規則を回避できます。 たとえば、Windows フォームで動作するクラスをフリー スレッドにする場合があります。 開発者が暗黙的な制限を理解している限り、フリー スレッド クラスを作成できます。 コンソール アプリケーションは、 Post 呼び出しの実行を同期しません。 これにより、 ProgressChanged イベントが順番に発生する可能性があります。 Post呼び出しの実行をシリアル化する場合は、System.Threading.SynchronizationContext クラスを実装してインストールします。

AsyncOperationAsyncOperationManagerを使用して非同期操作を有効にする方法の詳細については、「方法: イベント ベースの非同期パターンをサポートするコンポーネントを実装する」を参照してください。

ガイドライン

  • 理想的には、各メソッド呼び出しは他のメソッドから独立している必要があります。 呼び出しと共有リソースの結合は避ける必要があります。 リソースを呼び出し間で共有する場合は、実装で適切な同期メカニズムを提供する必要があります。

  • クライアントが同期を実装する必要がある設計は推奨されません。 たとえば、グローバル静的オブジェクトをパラメーターとして受け取る非同期メソッドを使用できます。このようなメソッドを複数回同時に呼び出すと、データの破損やデッドロックが発生する可能性があります。

  • 複数呼び出しオーバーロード (シグネチャにuserState ) を持つメソッドを実装する場合、クラスは、ユーザー状態のコレクション、またはタスク ID、および対応する保留中の操作を管理する必要があります。 さまざまな呼び出しによってコレクション内のオブジェクトuserState追加および削除されるため、このコレクションはlockリージョンで保護する必要があります。

  • 可能で適切な場合 CompletedEventArgs クラスを再利用することを検討してください。 この場合、指定されたデリゲートと EventArgs 型が 1 つのメソッドに関連付けられていないため、名前付けはメソッド名と一致しません。 ただし、開発者が EventArgs のプロパティから取得した値をキャストすることを強制することはできません。

  • Componentから派生するクラスを作成する場合は、独自のSynchronizationContext クラスを実装してインストールしないでください。 アプリケーション モデルは、コンポーネントではなく、使用される SynchronizationContext を制御します。

  • あらゆる種類のマルチスレッドを使用すると、非常に深刻で複雑なバグにさらされる可能性があります。 マルチスレッドを使用するソリューションを実装する前に、 マネージド スレッドのベスト プラクティスに関するページを参照してください。

こちらも参照ください