イベントベースの非同期パターンの実装

顕著な遅延が発生する可能性がある操作を伴うクラスを作成する場合は、イベント ベースの非同期パターンを実装することによって、非同期機能を与えることを検討します。

イベント ベースの非同期パターンは、非同期機能を持つクラスをパッケージ化するための標準的な方法を提供します。 AsyncOperationManager などのヘルパー クラスで実装しても、クラスは、ASP.NET、コンソール アプリケーション、Windows フォーム アプリケーションを含むすべてのアプリケーション モデルで正常に動作します。

イベント ベースの非同期パターンを実装する例については、「方法:イベントベースの非同期パターンをサポートするコンポーネントを実装する」を参照してください。

単純な非同期操作の場合は、BackgroundWorker コンポーネントが適切である可能性があります。 BackgroundWorker の詳細については、「方法: バックグラウンドで操作を実行する」を参照してください。

次の一覧は、このトピックで説明するイベント ベースの非同期パターンの機能を示しています。

  • イベントベースの非同期パターンを実装するチャンス

  • 非同期メソッドの名前付け

  • キャンセル処理の任意のサポート

  • IsBusy プロパティの任意のサポート

  • 進行状況レポートの任意のサポート

  • 増分の結果を返すことの任意のサポート

  • メソッドでの Out パラメーターと Ref パラメーターの処理

イベントベースの非同期パターンを実装するチャンス

以下に該当する場合は、イベントベースの非同期パターンの実装を検討します。

  • クラスのクライアントには、非同期操作に使用できる WaitHandleIAsyncResult オブジェクトは必要ありません。つまり、ポーリング、WaitAll または WaitAny は、クライアントによってビルドされる必要があるということです。

  • クライアントでの非同期操作を、使い慣れたイベント/デリゲート モデルを使用して管理したい。

どの操作も非同期を実装するための候補になりますが、長い待機時間が予想される操作を考慮してください。 特に適切なのは、クライアントがメソッドを呼び出し、完了時に通知を受け取ること以外の介入を必要としない操作です。 継続的に実行され、進捗状況、増分結果、または状態の変更をクライアントに定期的に通知する操作も適しています。

イベント ベースの非同期パターンをいつサポートするかを決定することの詳細については、「イベントベースの非同期パターンの実装時期を決定する」 (イベントベースの非同期パターンをいつ実装するかの決定) を参照してください。

非同期メソッドの名前付け

対応する非同期メソッドを用意する同期メソッド MethodName に対して、次の操作を行います。

以下を実行する MethodNameAsync メソッドを定義します。

  • void を返します。

  • MethodName メソッドと同じパラメーターを受け取ります。

  • 複数の呼び出しを受け入れます。

必要に応じて、MethodNameAsync と同一の MethodNameAsync オーバーロードを定義しますが、userState という追加のオブジェクト値パラメーターを使用して定義します。 これは、メソッドの複数の同時呼び出しを管理するために実行します。この場合、メソッドの呼び出しを区別するために userState 値がすべてのイベント ハンドラーに返されます。 単純に後で取得するためにユーザーの状態を格納する場所として実行することもできます。

個別の MethodNameAsync メソッドのシグネチャに対して、次の手順を実行します。

  1. メソッドと同じクラス内に次のイベントを定義します。

    Public Event MethodNameCompleted As MethodNameCompletedEventHandler
    
    public event MethodNameCompletedEventHandler MethodNameCompleted;
    
  2. 次のデリゲートと AsyncCompletedEventArgs を定義します。 これらは、クラス自体の外部に定義される可能性がありますが、同じ名前空間に定義されます。

    Public Delegate Sub MethodNameCompletedEventHandler( _
        ByVal sender As Object, _
        ByVal e As MethodNameCompletedEventArgs)
    
    Public Class MethodNameCompletedEventArgs
        Inherits System.ComponentModel.AsyncCompletedEventArgs
    Public ReadOnly Property Result() As MyReturnType
    End Property
    
    public delegate void MethodNameCompletedEventHandler(object sender,
        MethodNameCompletedEventArgs e);
    
    public class MethodNameCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs
    {
        public MyReturnType Result { get; }
    }
    
    • MethodNameCompletedEventArgs クラスで、フィールドではなくメンバーを読み取り専用プロパティとして公開していることを確認します。これは、フィールドはデータ バインドを妨げるためです。

    • 結果を生成しないメソッドに AsyncCompletedEventArgs の派生クラスを定義しないでください。 単純に AsyncCompletedEventArgs のインスタンス自体を使用します。

      注意

      実行可能かつ適切な場合、デリゲートと AsyncCompletedEventArgs 型を再利用することはまったく問題ありません。 この場合、特定のデリゲートと AsyncCompletedEventArgs が 1 つのメソッドに関連付けられることがないため、メソッド名のような名前の一貫性はなくなります。

キャンセル処理の任意のサポート

クラスで非同期操作のキャンセルをサポートする場合は、以下に示すようにキャンセルをクライアントに公開する必要があります。 キャンセルのサポートを定義する前に、以下の 2 点について判断する必要があります。

  • 今後の予想される追加機能を含め、クラスには、キャンセルをサポートする非同期操作が 1 つだけあるかどうか。
  • キャンセルをサポートする非同期操作が、複数の保留中の操作をサポートできるかどうか。 これは、MethodNameAsync メソッドが userState パラメーターを受け取り、いずれかの呼び出しが終了するまで待機する前に、複数の呼び出しを許可できるかどうかを意味します。

これら 2 つの質問に対する回答を次の表に当てはめて、キャンセル メソッドで使用するシグネチャを決定します。

Visual Basic

複数の同時操作をサポート 一度に 1 つだけの操作
クラス全体で 1 つの非同期操作 Sub MethodNameAsyncCancel(ByVal userState As Object) Sub MethodNameAsyncCancel()
クラスの複数の非同期操作 Sub CancelAsync(ByVal userState As Object) Sub CancelAsync()

C#

複数の同時操作をサポート 一度に 1 つだけの操作
クラス全体で 1 つの非同期操作 void MethodNameAsyncCancel(object userState); void MethodNameAsyncCancel();
クラスの複数の非同期操作 void CancelAsync(object userState); void CancelAsync();

CancelAsync(object userState) メソッドを定義する場合、クライアントは、1 つの非同期メソッドのすべての呼び出しの間だけではなく、オブジェクトに対して呼び出されたすべての非同期メソッドの間で区別できるようにする状態値を慎重に選択する必要があります。

1 つの非同期操作のバージョンの MethodNameAsyncCancel という名前は、Visual Studio の IntelliSense のようなデザイン環境でそのメソッドをより簡単に検出できるようにするために、このように決定されています。 これにより、関連するメンバーがグループ化され、非同期機能とは無関係の他のメンバーとは区別されます。 今後のバージョンで非同期操作を追加する可能性がある場合は、CancelAsync を定義することをお勧めします。

同じクラスに上の表の複数のメソッドを定義しないでください。 それは意味のない行為であるか、メソッドの増加によってクラス インターフェイスがわかりにくくなります。

これらのメソッドは、通常は直ちに結果が戻り、操作が実際にキャンセルされる場合もキャンセルされない場合もあります。 MethodNameCompleted イベント用のイベント ハンドラーでは、MethodNameCompletedEventArgs オブジェクトに Cancelled フィールドが含まれます。クライアントはこれを使用して、キャンセルが発生したかどうかを判断できます。

イベントベースの非同期パターンを実装するための推奨される手順」に説明されているキャンセルのセマンティクスに従ってください。

IsBusy プロパティの任意のサポート

クラスが複数の同時呼び出しをサポートしない場合は、IsBusy プロパティを公開することを検討してください。 開発者は、このプロパティによって、MethodNameAsync メソッドが MethodNameAsync メソッドからの例外をキャッチせずに実行されているかどうかを判断できます。

イベントベースの非同期パターンを実装するための推奨される手順」に説明されている IsBusy のセマンティクスに従ってください。

進行状況レポートの任意のサポート

非同期操作の操作中に進行状況を報告することをお勧めします。 イベント ベースの非同期パターンには、これを行うためのガイドラインがあります。

  • 必要に応じて、非同期操作によって発生し、適切なスレッドで呼び出されるイベントを定義します。 ProgressChangedEventArgs オブジェクトは、0 ~ 100 の範囲であることが期待されている整数値の進行状況インジケーターを伝達します。

  • このイベントには、次のように名前を付けます。

    • ProgressChanged: クラスに複数の非同期操作がある (または将来のバージョンで複数の非同期操作を含めることが予想される) 場合。

    • MethodNameProgressChanged クラスに非同期操作が 1 つだけある場合。

    この名前の付け方は、「キャンセルの任意のサポート」セクションで説明した、キャンセル メソッドに対する方法と対応しています。

このイベントは、ProgressChangedEventHandler デリゲート シグネチャと ProgressChangedEventArgs クラスを使用する必要があります。 別の方法として、ドメイン固有の進行状況インジケーター (読み取られたバイト数やダウンロード操作の合計バイト数など) を提供できる場合は、ProgressChangedEventArgs の派生クラスを定義します。

サポートされる非同期メソッドの数にかかわらず、クラスの ProgressChanged または MethodNameProgressChanged イベントは 1 つしかないことに注意してください。 クライアントは、MethodNameAsync メソッドに渡される userState オブジェクトを使用して、複数の同時操作に対する進行状況の更新を区別することが期待されています。

複数の操作で進行状況がサポートされ、それぞれが異なる進行状況インジケーターを返す状況が発生することがあります。 この場合は、1 つの ProgressChanged イベントでは不適切であり、複数の ProgressChanged のサポートを検討することができます。 この場合は、各 MethodNameAsync メソッドに対して、MethodNameProgressChanged の名前付けパターンを使用します。

イベントベースの非同期パターンを実装するための推奨される手順」に説明されている進行状況報告のセマンティクスに従ってください。

増分の結果を返すことの任意のサポート

場合によっては、非同期操作が完了する前に、増分結果が返ることがあります。 このシナリオをサポートするために使用できるオプションは多数あります。 以下に、いくつかの例を示します。

単一操作クラス

クラスが 1 つの非同期操作のみをサポートし、その操作が増分結果を返すことができる場合は、次のようにします。

  • ProgressChangedEventArgs 型を増分結果データを伝達するように拡張し、この拡張されたデータを使用する MethodNameProgressChanged イベントを定義します。

  • 報告する増分結果があるときに、この MethodNameProgressChanged イベントを発生させます。

このソリューションは、MethodNameProgressChanged イベントと同じように、同じイベントの発生で "すべての操作" に対して増分結果を返しても何の問題もないため、特に単一非同期操作のクラスに適用されます。

同じ型の増分結果を持つ複数操作クラス

この場合、クラスは、それぞれが増分結果を返すことができる複数の非同期メソッドをサポートし、増分結果はすべて同じ型のデータを持っています。

同じ EventArgs 構造体がすべての増分結果で機能するため、上記の単一操作クラスで説明したモデルに従います。 複数の非同期メソッドに適用するため、MethodNameProgressChanged イベントの代わりに ProgressChanged イベントを定義します。

異なる型の増分結果を持つ複数操作クラス

クラスが複数の非同期メソッドをサポートし、それぞれが異なる型のデータを返す場合は、以下を行います。

  • 増分結果の報告と進行状況の報告を分離します。

  • 各非同期メソッドの増分結果データを処理する個別の MethodNameProgressChanged イベントを、適切な EventArgs で定義します。

イベントベースの非同期パターンを実装するための推奨される手順」に説明されているように、そのイベント ハンドラーを適切なスレッドで呼び出します。

メソッドでの Out パラメーターと Ref パラメーターの処理

.NET での outref の使用は、一般に推奨されていませんが、それらが存在する場合は、次のルールに従います。

非同期メソッド MethodName の場合:

  • MethodName に対する out パラメーターは、MethodNameAsync の一部にしないでください。 代わりに、MethodName での同等のパラメーターと同じ名前の MethodNameCompletedEventArgs の一部にする必要があります (より適切な名前がない限り)。

  • MethodName に対する ref パラメーターは、MethodNameAsync の一部として出現し、MethodName での同等のパラメーターと同じ名前の MethodNameCompletedEventArgs の一部として出現する必要があります (より適切な名前がない限り)。

次に例を示します。

Public Function MethodName(ByVal arg1 As String, ByRef arg2 As String, ByRef arg3 As String) As Integer
public int MethodName(string arg1, ref string arg2, out string arg3);

非同期メソッドとその AsyncCompletedEventArgs クラスは、次のようになります。

Public Sub MethodNameAsync(ByVal arg1 As String, ByVal arg2 As String)

Public Class MethodNameCompletedEventArgs
    Inherits System.ComponentModel.AsyncCompletedEventArgs
    Public ReadOnly Property Result() As Integer
    End Property
    Public ReadOnly Property Arg2() As String
    End Property
    Public ReadOnly Property Arg3() As String
    End Property
End Class
public void MethodNameAsync(string arg1, string arg2);

public class MethodNameCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs
{
    public int Result { get; };
    public string Arg2 { get; };
    public string Arg3 { get; };
}

関連項目