イベントベースの非同期パターンの概要

多数のタスクを同時に実行しながら、ユーザーの操作にも応答するアプリケーションには、通常、複数のスレッドを使用するデザインが必要です。 System.Threading 名前空間は、高性能なマルチスレッド アプリケーションを作成するのに必要なすべてのツールを提供します。ただし、これらのツールを効果的に使用するには、マルチスレッド ソフトウェア エンジニアリングの豊富な経験が必要です。 比較的単純なマルチスレッド アプリケーションの場合は、BackgroundWorker コンポーネントが簡単なソリューションを提供します。 より高度な非同期アプリケーションの場合は、イベント ベースの非同期パターンに準拠したクラスの実装を検討してください。

イベント ベースの非同期パターンを使用すると、マルチスレッド デザイン固有の多くの複雑な問題を気にせずに、マルチスレッド アプリケーションの利点を活用できます。 このパターンをサポートするクラスを使用すると、次のことが可能になります。

  • ダウンロードやデータベース操作などの時間がかかるタスクを、アプリケーションを中断せずに、"バックグラウンド" で行うことができます。

  • 複数の操作を同時に実行し、それぞれの操作が完了するたびに通知を受け取ることができます。

  • アプリケーションを停止 ("ブロック") せずに、リソースが使用可能な状態になるまで待機できます。

  • 使い慣れたイベントおよびデリゲートのモデルを使用して、保留中の非同期操作と通信できます。 イベント ハンドラーおよびデリゲートの使い方の詳細については、イベントに関するページを参照してください。

イベント ベースの非同期パターンをサポートするクラスには、1 つまたは複数の MethodNameAsync という名前のメソッドが含まれます。 これらのメソッドは、同期バージョンに対応するもので、現在のスレッドで同じ操作を行います。 クラスには、MethodNameCompleted イベントや MethodNameAsyncCancel (または単に CancelAsync) メソッドが含まれる場合もあります。

PictureBox は、イベント ベースの非同期パターンをサポートする一般的なコンポーネントです。 イメージを同期的にダウンロードするには、その Loadメソッドを呼び出します。ただし、イメージのサイズが大きい場合や、ネットワークの接続速度が遅い場合は、ダウンロード操作が完了して Load の呼び出しが返されるまで、アプリケーションが応答を停止します。

イメージの読み込み中にもアプリケーションを実行し続けるには、LoadAsync メソッドを呼び出して、他のイベントを処理する場合と同様に、LoadCompleted イベントを処理します。 LoadAsync メソッドを呼び出すと、ダウンロードを別のスレッドで ("バックグラウンド" で) 続行しながら、アプリケーションを実行し続けることができます。 イメージの読み込み操作が完了すると、イベント ハンドラーが呼び出されます。イベント ハンドラーは、AsyncCompletedEventArgs パラメーターを調べて、ダウンロードが正常に完了したかどうかを確認できます。

イベント ベースの非同期パターンでは、非同期操作をキャンセルできる必要があります。PictureBox コントロールでは、その CancelAsync メソッドでこの要件をサポートしています。 CancelAsync を呼び出すと、保留中のダウンロードを停止する要求が送信されます。タスクがキャンセルされると、LoadCompleted イベントが発生します。

注意事項

CancelAsync 要求が作成されると同時に、ダウンロードが終了する可能性もあります。このような場合、Cancelled にはキャンセルの要求が反映されません。 これは競合状態と呼ばれる、マルチスレッド プログラミングの一般的な問題です。 マルチスレッド プログラミングの問題の詳細については、「マネージド スレッド処理のベスト プラクティス」 (管理されたスレッドのベスト プラクティス) を参照してください。

イベント ベースの非同期パターンの特性

イベント ベースの非同期パターンには、特定のクラスでサポートされている操作の複雑さに応じて、複数の形式があります。 最もシンプルなクラスには、単一の MethodNameAsync メソッドと、このメソッドに対応する MethodNameCompleted イベントが含まれる場合があります。 より複雑なクラスには、複数の MethodNameAsync メソッドと、それぞれに対応する MethodNameCompleted イベント、およびこれらのメソッドの同期バージョンが含まれる場合があります。 クラスでは、各非同期メソッドの、キャンセル、進行状況のレポート、およびインクリメンタル結果をオプションでサポートできます。

また、非同期メソッドでは複数の保留中の呼び出し (複数の同時呼び出し) をサポートして、他の保留中の操作が完了するまで、コードを何度も呼び出せるようにできます。 このような状況を適切に処理するには、アプリケーションで各操作の完了を追跡する必要があります。

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

SoundPlayer および PictureBox コンポーネントは、イベント ベースの非同期パターンのシンプルな実装です。 WebClient および BackgroundWorker コンポーネントは、イベント ベースの非同期パターンのより複雑な実装です。

パターンに準拠したクラス宣言の例を次に示します。

Public Class AsyncExample  
    ' Synchronous methods.  
    Public Function Method1(ByVal param As String) As Integer
    Public Sub Method2(ByVal param As Double)
  
    ' Asynchronous methods.  
    Overloads Public Sub Method1Async(ByVal param As String)
    Overloads Public Sub Method1Async(ByVal param As String, ByVal userState As Object)
    Public Event Method1Completed As Method1CompletedEventHandler  
  
    Overloads Public Sub Method2Async(ByVal param As Double)
    Overloads Public Sub Method2Async(ByVal param As Double, ByVal userState As Object)
    Public Event Method2Completed As Method2CompletedEventHandler  
  
    Public Sub CancelAsync(ByVal userState As Object)
  
    Public ReadOnly Property IsBusy () As Boolean  
  
    ' Class implementation not shown.  
End Class  
public class AsyncExample  
{  
    // Synchronous methods.  
    public int Method1(string param);  
    public void Method2(double param);  
  
    // Asynchronous methods.  
    public void Method1Async(string param);  
    public void Method1Async(string param, object userState);  
    public event Method1CompletedEventHandler Method1Completed;  
  
    public void Method2Async(double param);  
    public void Method2Async(double param, object userState);  
    public event Method2CompletedEventHandler Method2Completed;  
  
    public void CancelAsync(object userState);  
  
    public bool IsBusy { get; }  
  
    // Class implementation not shown.  
}  

架空の AsyncExample クラスには 2 つのメソッドがあり、いずれも同期および非同期の呼び出しをサポートしています。 同期のオーバーロードは、呼び出し元スレッドで操作を呼び出しおよび実行する任意のメソッドと同様に動作します。操作に時間がかかる場合は、呼び出しから戻るまでにかなりの遅延が発生する場合があります。 非同期のオーバーロードは、別のスレッドで操作を開始して即座に戻ります。これにより、操作を "バックグラウンド" で実行しながら、呼び出し元スレッドを続行できます。

非同期メソッドのオーバーロード

非同期操作には、基本的に単一呼び出しと複数呼び出しの 2 つのオーバーロードがあります。 これら 2 つの形式はそのメソッド シグネチャで区別できます。複数呼び出しの形式には、userState という名前の追加のパラメーターがあります。 この形式を使用すると、保留中の非同期操作が完了するのを待たずに、コードで Method1Async(string param, object userState) を複数回呼び出すことができます。 一方、直前の呼び出しが完了する前に Method1Async(string param) を呼び出そうとすると、メソッドにより InvalidOperationException が発生します。

複数呼び出しのオーバーロードの userState パラメーターにより、非同期操作を区別できます。 Method1Async(string param, object userState) の各呼び出しに一意な値 (GUID やハッシュ コードなど) を指定すると、各操作が完了したときに、イベント ハンドラーは、どの操作のインスタンスが完了イベントを発生させたのかを確認できます。

保留中の操作の追跡

複数呼び出しのオーバーロードを使用する場合は、保留中のタスクの userState オブジェクト (タスク ID) を追跡する必要があります。 Method1Async(string param, object userState) の呼び出しごとに、通常は新しい一意な userState オブジェクトを生成し、それをコレクションに追加します。 この userState オブジェクトに対応するタスクが完了イベントを発生させると、完了メソッドの実装は AsyncCompletedEventArgs.UserState を調べて、それをコレクションから削除します。 このように使用すると、userState パラメーターはタスク ID の役割を果たします。

注意

複数呼び出しのオーバーロードの呼び出しでは、userState に一意な値を指定するように注意が必要です。 タスク ID が一意でないと、非同期クラスが ArgumentException をスローします。

保留中の操作のキャンセル

非同期操作を完了前にいつでもキャンセルできることは重要です。 イベント ベースの非同期パターンを実装するクラスには、CancelAsync メソッド (非同期メソッドが 1 つだけの場合) または MethodNameAsyncCancel メソッド (複数の非同期メソッドがある場合) が含まれます。

複数呼び出しを可能にするメソッドは userState パラメーターを受け取ります。これは、各タスクの有効期間を追跡するのに使用できます。 CancelAsyncuserState パラメーターを受け取ります。このパラメーターにより、特定の保留中のタスクを取り消すことができます。

保留中の操作を一度に 1 つだけサポートする Method1Async(string param) のようなメソッドは、キャンセルできません。

進行状況の更新およびインクリメンタル結果の受信

イベント ベースの非同期パターンに準拠するクラスは、進行状況およびインクリメンタル結果を追跡するイベントをオプションで提供します。 通常、これは ProgressChanged または MethodNameProgressChanged という名前で、その対応するイベント ハンドラーは ProgressChangedEventArgs パラメーターを受け取ります。

ProgressChanged イベントのイベント ハンドラーは、ProgressChangedEventArgs.ProgressPercentage プロパティを調べて、非同期タスクの何パーセントが完了したのかを確認できます。 このプロパティは 0 ~ 100 の範囲です。これを使用すると、Valuear の ProgressBar プロパティを更新できます。 複数の非同期操作が保留中の場合は、ProgressChangedEventArgs.UserState プロパティを使用して、どの操作が進行状況をレポートしているのかを判別できます。

一部のクラスは、非同期操作の進行に応じて、インクリメンタル結果をレポートします。 これらの結果は ProgressChangedEventArgs から派生したクラス内に保存され、派生クラスのプロパティとして表示されます。 ProgressChanged イベントのイベント ハンドラーのこれらの結果へは、ProgressPercentage プロパティにアクセスするのと同じようにアクセスできます。 複数の非同期操作が保留中の場合は、UserState プロパティを使用して、どの操作がインクリメンタル結果をレポートしているのかを判別できます。

関連項目