方法: イベントベースの非同期パターンをサポートするコンポーネントを実装する

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

このチュートリアルでは、イベント ベースの非同期パターンを実装するコンポーネントの作成方法を示します。 これは、System.ComponentModel 名前空間のヘルパー クラスを使用して実装します。これにより、コンポーネントは任意のアプリケーション モデルで正常に動作します (ASP.NET、コンソール アプリケーション、Windows フォーム アプリケーションなど)。 また、このコンポーネントは、PropertyGrid コントロールや独自のカスタム デザイナーで設計可能です。

このチュートリアルを完了すると、素数を非同期に計算するアプリケーションが作成されます。 アプリケーションには、メイン ユーザー インターフェイス (UI) スレッドと各素数の計算用のスレッドがあります。 大きな数が素数かどうかを調べるにはかなりの時間がかかることがありますが、この遅延によってメイン UI スレッドが中断されることはなく、計算中もフォームは応答性を維持します。 いくつでも好きなだけ計算を同時に実行し、保留中の計算を選択的に取り消すことができます。

このチュートリアルでは、以下のタスクを行います。

  • コンポーネントの作成

  • パブリックの非同期イベントとデリゲートの定義

  • プライベート デリゲートの定義

  • パブリック イベントの実装

  • 完了メソッドの実装

  • ワーカー メソッドの実装

  • 開始メソッドとキャンセル メソッドの実装

このトピックのコードを単一のリストとしてコピーするには、「方法 : イベントベースの非同期パターンのクライアントを実装する」をご覧ください。

コンポーネントの作成

最初に、イベント ベースの非同期パターンを実装するコンポーネントを作成します。

コンポーネントを作成するには

  • Component を継承する PrimeNumberCalculator というクラスを作成します。

パブリックの非同期イベントとデリゲートの定義

コンポーネントは、イベントを使ってクライアントと通信します。 MethodNameCompleted イベントは非同期タスクの完了をクライアントに通知し、MethodNameProgressChanged イベントは非同期タスクの進行状況をクライアントに通知します。

コンポーネントのクライアント用の非同期イベントを定義するには:

  1. ファイルの先頭で 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
    
  2. 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)
    
  3. PrimeNumberCalculator クラスの定義で、進行状況と完了をクライアントに報告するイベントを宣言します。

    public event ProgressChangedEventHandler ProgressChanged;
    public event CalculatePrimeCompletedEventHandler CalculatePrimeCompleted;
    
    Public Event ProgressChanged _
        As ProgressChangedEventHandler
    Public Event CalculatePrimeCompleted _
        As CalculatePrimeCompletedEventHandler
    
  4. 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」を参照してください。

コンポーネントの内部非同期動作を実装するには:

  1. 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
    
  2. コンポーネントのコンストラクターで InitializeDelegates メソッドを呼び出します。

    public PrimeNumberCalculator()
    {
        InitializeComponent();
    
        InitializeDelegates();
    }
    
    Public Sub New()
    
        InitializeComponent()
    
        InitializeDelegates()
    
    End Sub
    
  3. 実際の処理を非同期に行う PrimeNumberCalculator クラスのデリゲートを宣言します。 このデリゲートは、数値が素数かどうかをテストするワーカー メソッドをラップします。 デリゲートは AsyncOperation パラメーターを受け取ります。このパラメーターは、非同期操作の有効期間を追跡するために使われます。

    private delegate void WorkerEventHandler(
        int numberToCheck,
        AsyncOperation asyncOp);
    
    Private Delegate Sub WorkerEventHandler( _
    ByVal numberToCheck As Integer, _
    ByVal asyncOp As AsyncOperation)
    
  4. 保留中の非同期操作の有効期間を管理するためのコレクションを作成します。 クライアントは操作の実行と完了を追跡する手段が必要であり、この追跡は、クライアントが非同期メソッドを呼び出すときに渡す必要のある一意のトークン (タスク ID) を使って行われます。 PrimeNumberCalculator コンポーネントは、タスク ID を対応する呼び出しと関連付けることによって、各呼び出しの追跡を維持する必要があります。 クライアントが一意でないタスク ID を渡した場合、PrimeNumberCalculator コンポーネントは例外を生成する必要があります。

    PrimeNumberCalculator コンポーネントは、HybridDictionary という特別なコレクション クラスを使ってタスク ID を追跡します。 クラスの定義で、userStateToLifetime という名前の HybridDictionary を作成します。

    private HybridDictionary userStateToLifetime =
        new HybridDictionary();
    
    Private userStateToLifetime As New HybridDictionary()
    

パブリック イベントの実装

イベント ベースの非同期パターンを実装するコンポーネントは、イベントを使ってクライアントに通信します。 これらのイベントは、AsyncOperation クラスを使って適切なスレッドで呼び出されます。

コンポーネントのクライアントに対するイベントを生成するには:

  1. クライアントに報告するためのパブリック イベントを実装します。 進行状況報告用のイベントと、完了報告用のイベントが必要です。

    // 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 が一意のクライアント トークンの内部コレクションから削除されます。 また、このメソッドは、対応する AsyncOperationPostOperationCompleted メソッドを呼び出すことにより、特定の非同期操作の有効期間を終了させます。 この呼び出しでは、アプリケーション モデルの適切なスレッドで完了イベントが発生します。 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 つのメソッド CalculateWorkerBuildPrimeNumberListIsPrime を実装します。 また、BuildPrimeNumberListIsPrime は、Sieve of Eratosthenes と呼ばれるよく知られたアルゴリズムを構成します。これは、テストする数値の平方根までのすべての素数を調べることで、その数値が素数かどうかを判定します。 平方根までに約数が見つからない場合、テスト対象の値は素数です。

このコンポーネントが最大限の効率性で作成されているとしたら、異なるテスト数値のさまざまな呼び出しによって検出されたすべての素数を記憶しています。 また、2、3、5 などの自明の約数もチェックします。 ただし、この例の目的は時間のかかる操作を非同期に実行する方法を示すことなので、これらの最適化は自習のために残しておきます。

CalculateWorker メソッドはデリゲートにラップされており、BeginInvoke の呼び出しとは非同期に呼び出されます。

注意

進行状況の報告は、BuildPrimeNumberList メソッドで実装されています。 高速なコンピューターでは、ProgressChanged イベントが立て続けに発生する可能性があります。 これらのイベントが生成されるクライアント スレッドは、このような状況を処理できる必要があります。 ユーザー インターフェイスのコードがメッセージであふれ、処理が追いつかなくなり、応答しなくなる可能性があります。 この状況を処理するユーザー インターフェイスの例については、「方法: イベントベースの非同期パターンのクライアントを実装する」をご覧ください。

素数の計算を非同期に実行するには:

  1. 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
    
  2. 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
    
  3. 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
    
  4. 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
    
  5. 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

この段階で、コンポーネントをビルドすることができます。

コンポーネントをテストするには

  • コンポーネントをコンパイルします。

    残っているのは、非同期操作を開始およびキャンセルするメソッドである CalculatePrimeAsyncCancelAsync です。

開始メソッドとキャンセル メソッドの実装

ラップするデリゲートで BeginInvoke を呼び出すことにより、専用のスレッドでワーカー メソッドを開始します。 特定の非同期操作の有効期間を管理するには、AsyncOperationManager ヘルパー クラスの CreateOperation メソッドを呼び出します。 このメソッドが返す AsyncOperation は、クライアントのイベント ハンドラーに対する呼び出しを適切なスレッドまたはコンテキストにマーシャリングします。

特定の保留中操作を取り消すには、対応する AsyncOperationPostOperationCompleted を呼び出します。 このメソッドはその操作を終了するので、それ以降に AsyncOperation を呼び出すと例外がスローされます。

開始とキャンセルの機能を実装するには:

  1. 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
    
  2. 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 などの自明の約数をテストすることにより、この例を改良することもできます。

関連項目