方法: イベントベースの非同期パターンをサポートするコンポーネントを実装する
顕著な遅延が発生する可能性がある操作を伴うクラスを作成する場合は、イベント ベースの非同期パターンを実装することによって、非同期機能を与えることを検討します。
このチュートリアルでは、イベント ベースの非同期パターンを実装するコンポーネントの作成方法を示します。 これは、System.ComponentModel 名前空間のヘルパー クラスを使用して実装します。これにより、コンポーネントは任意のアプリケーション モデルで正常に動作します (ASP.NET、コンソール アプリケーション、Windows フォーム アプリケーションなど)。 また、このコンポーネントは、PropertyGrid コントロールや独自のカスタム デザイナーで設計可能です。
このチュートリアルを完了すると、素数を非同期に計算するアプリケーションが作成されます。 アプリケーションには、メイン ユーザー インターフェイス (UI) スレッドと各素数の計算用のスレッドがあります。 大きな数が素数かどうかを調べるにはかなりの時間がかかることがありますが、この遅延によってメイン UI スレッドが中断されることはなく、計算中もフォームは応答性を維持します。 いくつでも好きなだけ計算を同時に実行し、保留中の計算を選択的に取り消すことができます。
このチュートリアルでは、以下のタスクを行います。
コンポーネントの作成
パブリックの非同期イベントとデリゲートの定義
プライベート デリゲートの定義
パブリック イベントの実装
完了メソッドの実装
ワーカー メソッドの実装
開始メソッドとキャンセル メソッドの実装
このトピックのコードを単一のリストとしてコピーするには、「方法 : イベントベースの非同期パターンのクライアントを実装する」をご覧ください。
コンポーネントの作成
最初に、イベント ベースの非同期パターンを実装するコンポーネントを作成します。
コンポーネントを作成するには
- Component を継承する
PrimeNumberCalculator
というクラスを作成します。
パブリックの非同期イベントとデリゲートの定義
コンポーネントは、イベントを使ってクライアントと通信します。 MethodNameCompleted イベントは非同期タスクの完了をクライアントに通知し、MethodNameProgressChanged イベントは非同期タスクの進行状況をクライアントに通知します。
コンポーネントのクライアント用の非同期イベントを定義するには:
ファイルの先頭で System.Threading および System.Collections.Specialized 名前空間をインポートします。
using System; using System.Collections; using System.Collections.Specialized; using System.ComponentModel; using System.Data; using System.Drawing; using System.Globalization; using System.Threading; using System.Windows.Forms;
Imports System.Collections Imports System.Collections.Specialized Imports System.ComponentModel Imports System.Drawing Imports System.Globalization Imports System.Threading Imports System.Windows.Forms
PrimeNumberCalculator
クラスの定義の前で、進行状況イベントと完了イベントのデリゲートを宣言します。public delegate void ProgressChangedEventHandler( ProgressChangedEventArgs e); public delegate void CalculatePrimeCompletedEventHandler( object sender, CalculatePrimeCompletedEventArgs e);
Public Delegate Sub ProgressChangedEventHandler( _ ByVal e As ProgressChangedEventArgs) Public Delegate Sub CalculatePrimeCompletedEventHandler( _ ByVal sender As Object, _ ByVal e As CalculatePrimeCompletedEventArgs)
PrimeNumberCalculator
クラスの定義で、進行状況と完了をクライアントに報告するイベントを宣言します。public event ProgressChangedEventHandler ProgressChanged; public event CalculatePrimeCompletedEventHandler CalculatePrimeCompleted;
Public Event ProgressChanged _ As ProgressChangedEventHandler Public Event CalculatePrimeCompleted _ As CalculatePrimeCompletedEventHandler
PrimeNumberCalculator
クラスの定義の後で、CalculatePrimeCompleted
イベントに対するクライアントのイベント ハンドラーに各計算の結果を報告するためのCalculatePrimeCompletedEventArgs
クラスを派生します。AsyncCompletedEventArgs
プロパティに加えて、このクラスにより、クライアントはテストされた値、その値が素数かどうか、素数でない場合は最初の約数を知ることができます。public class CalculatePrimeCompletedEventArgs : AsyncCompletedEventArgs { private int numberToTestValue = 0; private int firstDivisorValue = 1; private bool isPrimeValue; public CalculatePrimeCompletedEventArgs( int numberToTest, int firstDivisor, bool isPrime, Exception e, bool canceled, object state) : base(e, canceled, state) { this.numberToTestValue = numberToTest; this.firstDivisorValue = firstDivisor; this.isPrimeValue = isPrime; } public int NumberToTest { get { // Raise an exception if the operation failed or // was canceled. RaiseExceptionIfNecessary(); // If the operation was successful, return the // property value. return numberToTestValue; } } public int FirstDivisor { get { // Raise an exception if the operation failed or // was canceled. RaiseExceptionIfNecessary(); // If the operation was successful, return the // property value. return firstDivisorValue; } } public bool IsPrime { get { // Raise an exception if the operation failed or // was canceled. RaiseExceptionIfNecessary(); // If the operation was successful, return the // property value. return isPrimeValue; } } }
Public Class CalculatePrimeCompletedEventArgs Inherits AsyncCompletedEventArgs Private numberToTestValue As Integer = 0 Private firstDivisorValue As Integer = 1 Private isPrimeValue As Boolean Public Sub New( _ ByVal numberToTest As Integer, _ ByVal firstDivisor As Integer, _ ByVal isPrime As Boolean, _ ByVal e As Exception, _ ByVal canceled As Boolean, _ ByVal state As Object) MyBase.New(e, canceled, state) Me.numberToTestValue = numberToTest Me.firstDivisorValue = firstDivisor Me.isPrimeValue = isPrime End Sub Public ReadOnly Property NumberToTest() As Integer Get ' Raise an exception if the operation failed ' or was canceled. RaiseExceptionIfNecessary() ' If the operation was successful, return ' the property value. Return numberToTestValue End Get End Property Public ReadOnly Property FirstDivisor() As Integer Get ' Raise an exception if the operation failed ' or was canceled. RaiseExceptionIfNecessary() ' If the operation was successful, return ' the property value. Return firstDivisorValue End Get End Property Public ReadOnly Property IsPrime() As Boolean Get ' Raise an exception if the operation failed ' or was canceled. RaiseExceptionIfNecessary() ' If the operation was successful, return ' the property value. Return isPrimeValue End Get End Property End Class
チェックポイント 1
この段階で、コンポーネントをビルドすることができます。
コンポーネントをテストするには
コンポーネントをコンパイルします。
コンパイラの警告が 2 つ表示されます。
warning CS0067: The event 'AsynchronousPatternExample.PrimeNumberCalculator.ProgressChanged' is never used warning CS0067: The event 'AsynchronousPatternExample.PrimeNumberCalculator.CalculatePrimeCompleted' is never used
これらの警告は次のセクションでなくなります。
プライベート デリゲートの定義
PrimeNumberCalculator
コンポーネントの非同期の側面は、SendOrPostCallback と呼ばれる特別なデリゲートを使って内部的に実装されます。 SendOrPostCallback は、ThreadPool スレッドで実行するコールバック メソッドを表します。 コールバック メソッドは、Object 型のパラメーターを 1 つ受け取るシグネチャを持つ必要があります。つまり、ラッパー クラスでデリゲートに状態を渡す必要があります。 詳細については、「SendOrPostCallback」を参照してください。
コンポーネントの内部非同期動作を実装するには:
PrimeNumberCalculator
クラスで SendOrPostCallback デリゲートを宣言して作成します。InitializeDelegates
という名前のユーティリティ メソッドで SendOrPostCallback オブジェクトを作成します。2 つのデリゲートが必要です。1 つはクライアントに進行状況を報告するためのもので、もう 1 つはクライアントに完了を報告するためのものです。
private SendOrPostCallback onProgressReportDelegate; private SendOrPostCallback onCompletedDelegate;
Private onProgressReportDelegate As SendOrPostCallback Private onCompletedDelegate As SendOrPostCallback
protected virtual void InitializeDelegates() { onProgressReportDelegate = new SendOrPostCallback(ReportProgress); onCompletedDelegate = new SendOrPostCallback(CalculateCompleted); }
Protected Overridable Sub InitializeDelegates() onProgressReportDelegate = _ New SendOrPostCallback(AddressOf ReportProgress) onCompletedDelegate = _ New SendOrPostCallback(AddressOf CalculateCompleted) End Sub
コンポーネントのコンストラクターで
InitializeDelegates
メソッドを呼び出します。public PrimeNumberCalculator() { InitializeComponent(); InitializeDelegates(); }
Public Sub New() InitializeComponent() InitializeDelegates() End Sub
実際の処理を非同期に行う
PrimeNumberCalculator
クラスのデリゲートを宣言します。 このデリゲートは、数値が素数かどうかをテストするワーカー メソッドをラップします。 デリゲートは AsyncOperation パラメーターを受け取ります。このパラメーターは、非同期操作の有効期間を追跡するために使われます。private delegate void WorkerEventHandler( int numberToCheck, AsyncOperation asyncOp);
Private Delegate Sub WorkerEventHandler( _ ByVal numberToCheck As Integer, _ ByVal asyncOp As AsyncOperation)
保留中の非同期操作の有効期間を管理するためのコレクションを作成します。 クライアントは操作の実行と完了を追跡する手段が必要であり、この追跡は、クライアントが非同期メソッドを呼び出すときに渡す必要のある一意のトークン (タスク ID) を使って行われます。
PrimeNumberCalculator
コンポーネントは、タスク ID を対応する呼び出しと関連付けることによって、各呼び出しの追跡を維持する必要があります。 クライアントが一意でないタスク ID を渡した場合、PrimeNumberCalculator
コンポーネントは例外を生成する必要があります。PrimeNumberCalculator
コンポーネントは、HybridDictionary という特別なコレクション クラスを使ってタスク ID を追跡します。 クラスの定義で、userStateToLifetime
という名前の HybridDictionary を作成します。private HybridDictionary userStateToLifetime = new HybridDictionary();
Private userStateToLifetime As New HybridDictionary()
パブリック イベントの実装
イベント ベースの非同期パターンを実装するコンポーネントは、イベントを使ってクライアントに通信します。 これらのイベントは、AsyncOperation クラスを使って適切なスレッドで呼び出されます。
コンポーネントのクライアントに対するイベントを生成するには:
クライアントに報告するためのパブリック イベントを実装します。 進行状況報告用のイベントと、完了報告用のイベントが必要です。
// This method is invoked via the AsyncOperation object, // so it is guaranteed to be executed on the correct thread. private void CalculateCompleted(object operationState) { CalculatePrimeCompletedEventArgs e = operationState as CalculatePrimeCompletedEventArgs; OnCalculatePrimeCompleted(e); } // This method is invoked via the AsyncOperation object, // so it is guaranteed to be executed on the correct thread. private void ReportProgress(object state) { ProgressChangedEventArgs e = state as ProgressChangedEventArgs; OnProgressChanged(e); } protected void OnCalculatePrimeCompleted( CalculatePrimeCompletedEventArgs e) { if (CalculatePrimeCompleted != null) { CalculatePrimeCompleted(this, e); } } protected void OnProgressChanged(ProgressChangedEventArgs e) { if (ProgressChanged != null) { ProgressChanged(e); } }
' This method is invoked via the AsyncOperation object, ' so it is guaranteed to be executed on the correct thread. Private Sub CalculateCompleted(ByVal operationState As Object) Dim e As CalculatePrimeCompletedEventArgs = operationState OnCalculatePrimeCompleted(e) End Sub ' This method is invoked via the AsyncOperation object, ' so it is guaranteed to be executed on the correct thread. Private Sub ReportProgress(ByVal state As Object) Dim e As ProgressChangedEventArgs = state OnProgressChanged(e) End Sub Protected Sub OnCalculatePrimeCompleted( _ ByVal e As CalculatePrimeCompletedEventArgs) RaiseEvent CalculatePrimeCompleted(Me, e) End Sub Protected Sub OnProgressChanged( _ ByVal e As ProgressChangedEventArgs) RaiseEvent ProgressChanged(e) End Sub
完了メソッドの実装
完了デリゲートは、非同期操作が正常完了、エラー、またはキャンセルで終了すると、基になっているフリー スレッドの非同期動作が呼び出すメソッドです。 この呼び出しは、任意のスレッドで発生します。
このメソッドでは、クライアントのタスク ID が一意のクライアント トークンの内部コレクションから削除されます。 また、このメソッドは、対応する AsyncOperation で PostOperationCompleted メソッドを呼び出すことにより、特定の非同期操作の有効期間を終了させます。 この呼び出しでは、アプリケーション モデルの適切なスレッドで完了イベントが発生します。 PostOperationCompleted メソッドが呼び出された後、AsyncOperation のこのインスタンスは使えなくなり、それ以降にこれを使おうとするとすべて例外がスローされます。
CompletionMethod
のシグネチャは、非同期操作の結果を記述するために必要なすべての状態を保持する必要があります。 この特定の非同期操作によってテストされた値の状態、その値が素数かどうか、素数の場合は最初の約数を保持しています。 また、発生した例外を記述する状態、およびこの特定のタスクに対応する AsyncOperation も保持します。
非同期操作を完了するには:
完了メソッドを実装します。 このメソッドは 6 つのパラメーターを受け取り、それを使って、クライアントの
CalculatePrimeCompletedEventHandler
によってクライアントに返されるCalculatePrimeCompletedEventArgs
を設定します。 また、クライアントのタスク ID トークンを内部コレクションから削除し、PostOperationCompleted を呼び出して非同期操作の有効期間を終了します。 AsyncOperation は、アプリケーション モデルに適したスレッドまたはコンテキストへの呼び出しをマーシャリングします。// This is the method that the underlying, free-threaded // asynchronous behavior will invoke. This will happen on // an arbitrary thread. private void CompletionMethod( int numberToTest, int firstDivisor, bool isPrime, Exception exception, bool canceled, AsyncOperation asyncOp ) { // If the task was not previously canceled, // remove the task from the lifetime collection. if (!canceled) { lock (userStateToLifetime.SyncRoot) { userStateToLifetime.Remove(asyncOp.UserSuppliedState); } } // Package the results of the operation in a // CalculatePrimeCompletedEventArgs. CalculatePrimeCompletedEventArgs e = new CalculatePrimeCompletedEventArgs( numberToTest, firstDivisor, isPrime, exception, canceled, asyncOp.UserSuppliedState); // End the task. The asyncOp object is responsible // for marshaling the call. asyncOp.PostOperationCompleted(onCompletedDelegate, e); // Note that after the call to OperationCompleted, // asyncOp is no longer usable, and any attempt to use it // will cause an exception to be thrown. }
' This is the method that the underlying, free-threaded ' asynchronous behavior will invoke. This will happen on ' an arbitrary thread. Private Sub CompletionMethod( _ ByVal numberToTest As Integer, _ ByVal firstDivisor As Integer, _ ByVal prime As Boolean, _ ByVal exc As Exception, _ ByVal canceled As Boolean, _ ByVal asyncOp As AsyncOperation) ' If the task was not previously canceled, ' remove the task from the lifetime collection. If Not canceled Then SyncLock userStateToLifetime.SyncRoot userStateToLifetime.Remove(asyncOp.UserSuppliedState) End SyncLock End If ' Package the results of the operation in a ' CalculatePrimeCompletedEventArgs. Dim e As New CalculatePrimeCompletedEventArgs( _ numberToTest, _ firstDivisor, _ prime, _ exc, _ canceled, _ asyncOp.UserSuppliedState) ' End the task. The asyncOp object is responsible ' for marshaling the call. asyncOp.PostOperationCompleted(onCompletedDelegate, e) ' Note that after the call to PostOperationCompleted, asyncOp ' is no longer usable, and any attempt to use it will cause. ' an exception to be thrown. End Sub
チェックポイント 2
この段階で、コンポーネントをビルドすることができます。
コンポーネントをテストするには
コンポーネントをコンパイルします。
コンパイラの警告が 1 つ表示されます。
warning CS0169: The private field 'AsynchronousPatternExample.PrimeNumberCalculator.workerDelegate' is never used
この警告は次のセクションで解決されます。
ワーカー メソッドの実装
ここまでで、PrimeNumberCalculator
コンポーネントをサポートする非同期コードを実装できました。 次に、実際の処理を行うコードを実装できます。 3 つのメソッド CalculateWorker
、BuildPrimeNumberList
、IsPrime
を実装します。 また、BuildPrimeNumberList
と IsPrime
は、Sieve of Eratosthenes と呼ばれるよく知られたアルゴリズムを構成します。これは、テストする数値の平方根までのすべての素数を調べることで、その数値が素数かどうかを判定します。 平方根までに約数が見つからない場合、テスト対象の値は素数です。
このコンポーネントが最大限の効率性で作成されているとしたら、異なるテスト数値のさまざまな呼び出しによって検出されたすべての素数を記憶しています。 また、2、3、5 などの自明の約数もチェックします。 ただし、この例の目的は時間のかかる操作を非同期に実行する方法を示すことなので、これらの最適化は自習のために残しておきます。
CalculateWorker
メソッドはデリゲートにラップされており、BeginInvoke
の呼び出しとは非同期に呼び出されます。
注意
進行状況の報告は、BuildPrimeNumberList
メソッドで実装されています。 高速なコンピューターでは、ProgressChanged
イベントが立て続けに発生する可能性があります。 これらのイベントが生成されるクライアント スレッドは、このような状況を処理できる必要があります。 ユーザー インターフェイスのコードがメッセージであふれ、処理が追いつかなくなり、応答しなくなる可能性があります。 この状況を処理するユーザー インターフェイスの例については、「方法: イベントベースの非同期パターンのクライアントを実装する」をご覧ください。
素数の計算を非同期に実行するには:
TaskCanceled
ユーティリティ メソッドを実装します。 このメソッドは、タスク有効期間コレクションで指定されたタスク ID をチェックし、タスク ID が見つからない場合はtrue
を返します。// Utility method for determining if a // task has been canceled. private bool TaskCanceled(object taskId) { return( userStateToLifetime[taskId] == null ); }
' Utility method for determining if a ' task has been canceled. Private Function TaskCanceled(ByVal taskId As Object) As Boolean Return (userStateToLifetime(taskId) Is Nothing) End Function
CalculateWorker
メソッドを実装します。 このメソッドは、テストする数と AsyncOperation の 2 つのパラメーターを受け取ります。// This method performs the actual prime number computation. // It is executed on the worker thread. private void CalculateWorker( int numberToTest, AsyncOperation asyncOp) { bool isPrime = false; int firstDivisor = 1; Exception e = null; // Check that the task is still active. // The operation may have been canceled before // the thread was scheduled. if (!TaskCanceled(asyncOp.UserSuppliedState)) { try { // Find all the prime numbers up to // the square root of numberToTest. ArrayList primes = BuildPrimeNumberList( numberToTest, asyncOp); // Now we have a list of primes less than // numberToTest. isPrime = IsPrime( primes, numberToTest, out firstDivisor); } catch (Exception ex) { e = ex; } } //CalculatePrimeState calcState = new CalculatePrimeState( // numberToTest, // firstDivisor, // isPrime, // e, // TaskCanceled(asyncOp.UserSuppliedState), // asyncOp); //this.CompletionMethod(calcState); this.CompletionMethod( numberToTest, firstDivisor, isPrime, e, TaskCanceled(asyncOp.UserSuppliedState), asyncOp); //completionMethodDelegate(calcState); }
' This method performs the actual prime number computation. ' It is executed on the worker thread. Private Sub CalculateWorker( _ ByVal numberToTest As Integer, _ ByVal asyncOp As AsyncOperation) Dim prime As Boolean = False Dim firstDivisor As Integer = 1 Dim exc As Exception = Nothing ' Check that the task is still active. ' The operation may have been canceled before ' the thread was scheduled. If Not Me.TaskCanceled(asyncOp.UserSuppliedState) Then Try ' Find all the prime numbers up to the ' square root of numberToTest. Dim primes As ArrayList = BuildPrimeNumberList( _ numberToTest, asyncOp) ' Now we have a list of primes less than 'numberToTest. prime = IsPrime( _ primes, _ numberToTest, _ firstDivisor) Catch ex As Exception exc = ex End Try End If Me.CompletionMethod( _ numberToTest, _ firstDivisor, _ prime, _ exc, _ TaskCanceled(asyncOp.UserSuppliedState), _ asyncOp) End Sub
BuildPrimeNumberList
を実装します。 このメソッドは、テストする数と AsyncOperation の 2 つのパラメーターを受け取ります。 AsyncOperation を使って、進行状況とインクリメンタルな結果を報告します。 これにより、アプリケーション モデルの適切なスレッドまたはコンテキストで、クライアントのイベント ハンドラーが呼び出されます。BuildPrimeNumberList
は、素数を見つけると、ProgressChanged
イベントに対するクライアントのイベント ハンドラーに、インクリメンタルな結果としてこれを報告します。 これには ProgressChangedEventArgs から派生したCalculatePrimeProgressChangedEventArgs
という名前のクラスが必要であり、このクラスにはLatestPrimeNumber
という名前の追加プロパティが 1 つあります。BuildPrimeNumberList
メソッドはTaskCanceled
メソッドも定期的に呼び出し、このメソッドがtrue
を返すと終了します。// This method computes the list of prime numbers used by the // IsPrime method. private ArrayList BuildPrimeNumberList( int numberToTest, AsyncOperation asyncOp) { ProgressChangedEventArgs e = null; ArrayList primes = new ArrayList(); int firstDivisor; int n = 5; // Add the first prime numbers. primes.Add(2); primes.Add(3); // Do the work. while (n < numberToTest && !TaskCanceled( asyncOp.UserSuppliedState ) ) { if (IsPrime(primes, n, out firstDivisor)) { // Report to the client that a prime was found. e = new CalculatePrimeProgressChangedEventArgs( n, (int)((float)n / (float)numberToTest * 100), asyncOp.UserSuppliedState); asyncOp.Post(this.onProgressReportDelegate, e); primes.Add(n); // Yield the rest of this time slice. Thread.Sleep(0); } // Skip even numbers. n += 2; } return primes; }
' This method computes the list of prime numbers used by the ' IsPrime method. Private Function BuildPrimeNumberList( _ ByVal numberToTest As Integer, _ ByVal asyncOp As AsyncOperation) As ArrayList Dim e As ProgressChangedEventArgs = Nothing Dim primes As New ArrayList Dim firstDivisor As Integer Dim n As Integer = 5 ' Add the first prime numbers. primes.Add(2) primes.Add(3) ' Do the work. While n < numberToTest And _ Not Me.TaskCanceled(asyncOp.UserSuppliedState) If IsPrime(primes, n, firstDivisor) Then ' Report to the client that you found a prime. e = New CalculatePrimeProgressChangedEventArgs( _ n, _ CSng(n) / CSng(numberToTest) * 100, _ asyncOp.UserSuppliedState) asyncOp.Post(Me.onProgressReportDelegate, e) primes.Add(n) ' Yield the rest of this time slice. Thread.Sleep(0) End If ' Skip even numbers. n += 2 End While Return primes End Function
IsPrime
を実装します。 このメソッドは、既知の素数のリスト、テスト対象の数、および見つかった最初の約数の出力パラメーターの、3 つのパラメーターを受け取ります。 素数のリストを基に、テスト対象の数が素数かどうかを特定します。// This method tests n for primality against the list of // prime numbers contained in the primes parameter. private bool IsPrime( ArrayList primes, int n, out int firstDivisor) { bool foundDivisor = false; bool exceedsSquareRoot = false; int i = 0; int divisor = 0; firstDivisor = 1; // Stop the search if: // there are no more primes in the list, // there is a divisor of n in the list, or // there is a prime that is larger than // the square root of n. while ( (i < primes.Count) && !foundDivisor && !exceedsSquareRoot) { // The divisor variable will be the smallest // prime number not yet tried. divisor = (int)primes[i++]; // Determine whether the divisor is greater // than the square root of n. if (divisor * divisor > n) { exceedsSquareRoot = true; } // Determine whether the divisor is a factor of n. else if (n % divisor == 0) { firstDivisor = divisor; foundDivisor = true; } } return !foundDivisor; }
' This method tests n for primality against the list of ' prime numbers contained in the primes parameter. Private Function IsPrime( _ ByVal primes As ArrayList, _ ByVal n As Integer, _ ByRef firstDivisor As Integer) As Boolean Dim foundDivisor As Boolean = False Dim exceedsSquareRoot As Boolean = False Dim i As Integer = 0 Dim divisor As Integer = 0 firstDivisor = 1 ' Stop the search if: ' there are no more primes in the list, ' there is a divisor of n in the list, or ' there is a prime that is larger than ' the square root of n. While i < primes.Count AndAlso _ Not foundDivisor AndAlso _ Not exceedsSquareRoot ' The divisor variable will be the smallest prime number ' not yet tried. divisor = primes(i) i = i + 1 ' Determine whether the divisor is greater than the ' square root of n. If divisor * divisor > n Then exceedsSquareRoot = True ' Determine whether the divisor is a factor of n. ElseIf n Mod divisor = 0 Then firstDivisor = divisor foundDivisor = True End If End While Return Not foundDivisor End Function
ProgressChangedEventArgs から
CalculatePrimeProgressChangedEventArgs
を派生します。 このクラスは、ProgressChanged
イベントに対するクライアントのイベント ハンドラーにインクリメンタルな結果を報告するために必要です。LatestPrimeNumber
という名前の追加プロパティが 1 つあります。public class CalculatePrimeProgressChangedEventArgs : ProgressChangedEventArgs { private int latestPrimeNumberValue = 1; public CalculatePrimeProgressChangedEventArgs( int latestPrime, int progressPercentage, object userToken) : base( progressPercentage, userToken ) { this.latestPrimeNumberValue = latestPrime; } public int LatestPrimeNumber { get { return latestPrimeNumberValue; } } }
Public Class CalculatePrimeProgressChangedEventArgs Inherits ProgressChangedEventArgs Private latestPrimeNumberValue As Integer = 1 Public Sub New( _ ByVal latestPrime As Integer, _ ByVal progressPercentage As Integer, _ ByVal UserState As Object) MyBase.New(progressPercentage, UserState) Me.latestPrimeNumberValue = latestPrime End Sub Public ReadOnly Property LatestPrimeNumber() As Integer Get Return latestPrimeNumberValue End Get End Property End Class
チェックポイント 3
この段階で、コンポーネントをビルドすることができます。
コンポーネントをテストするには
コンポーネントをコンパイルします。
残っているのは、非同期操作を開始およびキャンセルするメソッドである
CalculatePrimeAsync
とCancelAsync
です。
開始メソッドとキャンセル メソッドの実装
ラップするデリゲートで BeginInvoke
を呼び出すことにより、専用のスレッドでワーカー メソッドを開始します。 特定の非同期操作の有効期間を管理するには、AsyncOperationManager ヘルパー クラスの CreateOperation メソッドを呼び出します。 このメソッドが返す AsyncOperation は、クライアントのイベント ハンドラーに対する呼び出しを適切なスレッドまたはコンテキストにマーシャリングします。
特定の保留中操作を取り消すには、対応する AsyncOperation で PostOperationCompleted を呼び出します。 このメソッドはその操作を終了するので、それ以降に AsyncOperation を呼び出すと例外がスローされます。
開始とキャンセルの機能を実装するには:
CalculatePrimeAsync
メソッドを実装します。 クライアントが提供したトークン (タスク ID) が、現在保留中のタスクを表すすべてのトークンの間で一意であることを確認します。 クライアントが一意ではないトークンを渡した場合、CalculatePrimeAsync
は例外を生成します。 一意の場合は、トークンはタスク ID のコレクションに追加されます。// This method starts an asynchronous calculation. // First, it checks the supplied task ID for uniqueness. // If taskId is unique, it creates a new WorkerEventHandler // and calls its BeginInvoke method to start the calculation. public virtual void CalculatePrimeAsync( int numberToTest, object taskId) { // Create an AsyncOperation for taskId. AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(taskId); // Multiple threads will access the task dictionary, // so it must be locked to serialize access. lock (userStateToLifetime.SyncRoot) { if (userStateToLifetime.Contains(taskId)) { throw new ArgumentException( "Task ID parameter must be unique", "taskId"); } userStateToLifetime[taskId] = asyncOp; } // Start the asynchronous operation. WorkerEventHandler workerDelegate = new WorkerEventHandler(CalculateWorker); workerDelegate.BeginInvoke( numberToTest, asyncOp, null, null); }
' This method starts an asynchronous calculation. ' First, it checks the supplied task ID for uniqueness. ' If taskId is unique, it creates a new WorkerEventHandler ' and calls its BeginInvoke method to start the calculation. Public Overridable Sub CalculatePrimeAsync( _ ByVal numberToTest As Integer, _ ByVal taskId As Object) ' Create an AsyncOperation for taskId. Dim asyncOp As AsyncOperation = _ AsyncOperationManager.CreateOperation(taskId) ' Multiple threads will access the task dictionary, ' so it must be locked to serialize access. SyncLock userStateToLifetime.SyncRoot If userStateToLifetime.Contains(taskId) Then Throw New ArgumentException( _ "Task ID parameter must be unique", _ "taskId") End If userStateToLifetime(taskId) = asyncOp End SyncLock ' Start the asynchronous operation. Dim workerDelegate As New WorkerEventHandler( _ AddressOf CalculateWorker) workerDelegate.BeginInvoke( _ numberToTest, _ asyncOp, _ Nothing, _ Nothing) End Sub
CancelAsync
メソッドを実装します。 トークン コレクションにtaskId
パラメーターが存在する場合は、削除されます。 これにより、開始する前に取り消されたタスクが実行するのを防ぎます。 タスクが実行中の場合、BuildPrimeNumberList
メソッドは、タスク ID が有効期間コレクションから削除されたことを検出すると終了します。// This method cancels a pending asynchronous operation. public void CancelAsync(object taskId) { AsyncOperation asyncOp = userStateToLifetime[taskId] as AsyncOperation; if (asyncOp != null) { lock (userStateToLifetime.SyncRoot) { userStateToLifetime.Remove(taskId); } } }
' This method cancels a pending asynchronous operation. Public Sub CancelAsync(ByVal taskId As Object) Dim obj As Object = userStateToLifetime(taskId) If (obj IsNot Nothing) Then SyncLock userStateToLifetime.SyncRoot userStateToLifetime.Remove(taskId) End SyncLock End If End Sub
チェックポイント 4
この段階で、コンポーネントをビルドすることができます。
コンポーネントをテストするには
- コンポーネントをコンパイルします。
PrimeNumberCalculator
コンポーネントが完全して使用できるようになります。
PrimeNumberCalculator
コンポーネントを使うクライアントの例については、「方法: イベントベースの非同期パターンのクライアントを実装する」をご覧ください。
次の手順
CalculatePrimeAsync
メソッドに相当する同期メソッドである CalculatePrime
を作成して、この例を拡張できます。 このようにすると、PrimeNumberCalculator
コンポーネントはイベント ベースの非同期パターンに完全に準拠するようになります。
異なるテスト対象の数に対するさまざまな呼び出しによって検出されたすべての素数のリストを保持することで、この例を改良できます。 この方法を使うと、各タスクはそれより前のタスクで行われた作業の結果を流用できます。 異なるスレッドによるリストへのアクセスがシリアル化されるように、このリストを lock
領域で保護することに注意してください。
2、3、5 などの自明の約数をテストすることにより、この例を改良することもできます。
関連項目
.NET