チュートリアル : イベントベースの非同期パターンをサポートするコンポーネントの実装

更新 : 2007 年 11 月

一部の操作によって著しい遅延が発生する可能性があるクラスを記述する場合は、イベントベースの非同期パターンの概要 の実装による非同期機能の追加を検討してください。

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

これが終了すると、素数を非同期的に計算するアプリケーションの用意ができます。アプリケーションには、メインのユーザー インターフェイス (UI) スレッドと、各素数計算のためのスレッドが含まれます。大きな数が素数かどうかのテストには時間がかかりますが、メインの UI スレッドがこの遅延によって中断されることはなく、計算中もフォームは応答可能な状態です。同時にいくつでも計算を実行でき、保留中の計算を個別に選択してキャンセルできます。

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

  • コンポーネントの作成

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

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

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

  • 完了メソッドの実装

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

  • 開始およびキャンセル メソッドの実装

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

コンポーネントの作成

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

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

  • Component から継承した、PrimeNumberCalculator という名前のクラスを作成します。

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

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

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

  1. System.Threading および System.Collections.Specialized 名前空間をファイルの先頭にインポートします。

    Imports System
    Imports System.Collections
    Imports System.Collections.Specialized
    Imports System.ComponentModel
    Imports System.Drawing
    Imports System.Globalization
    Imports System.Threading
    Imports System.Windows.Forms
    
    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;
    
    import System.*;
    import System.Collections.*;
    import System.Collections.Specialized.*;
    import System.ComponentModel.*;
    import System.Data.*;
    import System.Drawing.*;
    import System.Threading.*;
    import System.Windows.Forms.*;
    
    
  2. PrimeNumberCalculator クラスの定義の前に、進行状況および完了のイベントのデリゲートを宣言します。

    Public Delegate Sub ProgressChangedEventHandler( _
        ByVal e As ProgressChangedEventArgs)
    
    Public Delegate Sub CalculatePrimeCompletedEventHandler( _
        ByVal sender As Object, _
        ByVal e As CalculatePrimeCompletedEventArgs)
    
    public delegate void ProgressChangedEventHandler(
        ProgressChangedEventArgs e);
    
    public delegate void CalculatePrimeCompletedEventHandler(
        object sender,
        CalculatePrimeCompletedEventArgs e);
    
    public delegate void ProgressChangedEventHandler(ProgressChangedEventArgs e);
    
    /** @delegate 
     */
    public delegate void CalculatePrimeCompletedEventHandler(Object sender, 
        CalculatePrimeCompletedEventArgs e);
    
  3. PrimeNumberCalculator クラスの定義の前に、進行状況および完了をクライアントにレポートするイベントを宣言します。

    Public Event ProgressChanged _
        As ProgressChangedEventHandler
    Public Event CalculatePrimeCompleted _
        As CalculatePrimeCompletedEventHandler
    
    public event ProgressChangedEventHandler ProgressChanged;
    public event CalculatePrimeCompletedEventHandler CalculatePrimeCompleted;
    
    public ProgressChangedEventHandler progressChanged = null;
    /** @event 
     */
    public void add_ProgressChanged(ProgressChangedEventHandler p)
    {
        progressChanged =(ProgressChangedEventHandler)System.Delegate.
            Combine(progressChanged, p);
    } 
    
    /** @event 
     */
    public void remove_ProgressChanged(ProgressChangedEventHandler p)
    {
        progressChanged =(ProgressChangedEventHandler)System.Delegate.
            Remove(progressChanged, p);
    } 
    
    public CalculatePrimeCompletedEventHandler calculatePrimeCompleted = null;
    /** @event 
     */
    public void add_CalculatePrimeCompleted(CalculatePrimeCompletedEventHandler p)
    {
        calculatePrimeCompleted =(CalculatePrimeCompletedEventHandler)
            System.Delegate.Combine(calculatePrimeCompleted, p);
    } 
    
    /** @event 
     */
    public void remove_CalculatePrimeCompleted(
        CalculatePrimeCompletedEventHandler p)
    {
        calculatePrimeCompleted =(CalculatePrimeCompletedEventHandler)
            System.Delegate.Remove(calculatePrimeCompleted, p);
    } 
    
  4. PrimeNumberCalculator クラスの定義の後に、 CalculatePrimeCompletedEventArgs クラスを派生させて、各計算の結果を CalculatePrimeCompleted.event のクライアントのイベント ハンドラにレポートします。AsyncCompletedEventArgs プロパティに加えて、このクラスにより、クライアントはどの数値がテストされたのか、素数かどうか、素数でない場合は最初の除数は何かを判断できます。

    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
    
    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 extends AsyncCompletedEventArgs
    {
        private int numberToTestValue = 0;
        private int firstDivisorValue = 1;
        private boolean isPrimeValue;
    
        public CalculatePrimeCompletedEventArgs(int numberToTest, int firstDivisor,
            boolean isPrime, System.Exception e, boolean cancelled, Object state)
        {
            super(e, cancelled, state);
            this.numberToTestValue = numberToTest;
            this.firstDivisorValue = firstDivisor;
            this.isPrimeValue = isPrime;
        } 
    
        /** @property 
         */
        public int get_NumberToTest()
        {
            // Raise an exception if the operation failed or 
            // was cancelled.
            RaiseExceptionIfNecessary();
    
            // If the operation was successful, return the 
            // property value.
            return numberToTestValue;
        }
    
        /** @property 
         */
        public int get_FirstDivisor()
        {
            // Raise an exception if the operation failed or 
            // was cancelled.
            RaiseExceptionIfNecessary();
    
            // If the operation was successful, return the 
            // property value.
            return firstDivisorValue;
        }
    
        /** @property 
         */
        public boolean get_IsPrime()
        {
            // Raise an exception if the operation failed or 
            // was cancelled.
            RaiseExceptionIfNecessary();
    
            // If the operation was successful, return the 
            // property value.
            return isPrimeValue;
        } 
    } 
    

チェックポイント

この時点で、コンポーネントをビルドできます。

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

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

    次の 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 の単一のパラメータを受け取るシグネチャが必要です。つまり、デリゲート間の状態をラッパー クラスに渡す必要があります。詳細については、SendOrPostCallback のトピックを参照してください。

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

  1. PrimeNumberCalculator クラス内に SendOrPostCallback デリゲートを宣言および作成します。 InitializeDelegates という名前のユーティリティ メソッド内に、SendOrPostCallback オブジェクトを作成します。

    クライアントに進行状況を報告するためと、クライアントに完了を報告するための 2 つのデリゲートが必要です。

    Private onProgressReportDelegate As SendOrPostCallback
    Private onCompletedDelegate As SendOrPostCallback
    
    
    ...
    
    
    Protected Overridable Sub InitializeDelegates()
        onProgressReportDelegate = _
            New SendOrPostCallback(AddressOf ReportProgress)
        onCompletedDelegate = _
            New SendOrPostCallback(AddressOf CalculateCompleted)
    End Sub
    
    private SendOrPostCallback onProgressReportDelegate;
    private SendOrPostCallback onCompletedDelegate;
    
    
    ...
    
    
    protected virtual void InitializeDelegates()
    {
        onProgressReportDelegate =
            new SendOrPostCallback(ReportProgress);
        onCompletedDelegate =
            new SendOrPostCallback(CalculateCompleted);
    }
    
    private SendOrPostCallback onProgressReportDelegate;
    private SendOrPostCallback onCompletedDelegate;
    
    
    ...
    
    
    protected void InitializeDelegates()
    {
        onProgressReportDelegate = new SendOrPostCallback(ReportProgress);
        onCompletedDelegate = new SendOrPostCallback(CalculateCompleted);
    } 
    
    
  2. コンポーネントのコンストラクタ内で InitializeDelegates メソッドを呼び出します。

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

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

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

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

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

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

コンポーネントのクライアントに対してイベントを発生させるには

  • クライアントにレポートするために、パブリック イベントを実装します。進行状況をレポートするためのイベントと、完了をレポートするためのイベントが必要です。

    ' 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
    
    // 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);
        }
    }
    
    // You are guaranteed to be on the correct thread, as
    // this method is invoked via the AsyncOperation object.
    private void CalculateCompleted(Object operationState)
    {
        CalculatePrimeCompletedEventArgs e = 
            (CalculatePrimeCompletedEventArgs)operationState;
        OnCalculatePrimeCompleted(e);
    } 
    
    // You are guaranteed to be on the correct thread, as
    // this method is invoked via the AsyncOperation object.
    private void ReportProgress(Object state)
    {
        ProgressChangedEventArgs e = (ProgressChangedEventArgs)state;
        OnProgressChanged(e);
    } 
    
    protected void OnCalculatePrimeCompleted(CalculatePrimeCompletedEventArgs e)
    {
        if (calculatePrimeCompleted != null) {
            calculatePrimeCompleted.Invoke(this, e);
        }
    } 
    
    protected void OnProgressChanged(ProgressChangedEventArgs e)
    {
        if (progressChanged != null) {
            progressChanged.Invoke(e);
        }
    } 
    

完了メソッドの実装

完了デリゲートとは、非同期操作が正常な完了、エラー、またはキャンセルによって終了したときに、基になるフリースレッドの非同期動作を呼び出すメソッドです。この呼び出しは任意のスレッドで発生します。

このメソッドでは、一意なクライアント トークンの内部コレクションからクライアントのタスク ID を削除します。また、このメソッドは、対応する AsyncOperationPostOperationCompleted メソッドを呼び出すことにより、特定の非同期操作の有効期間を終了します。この呼び出しにより、アプリケーション モデルに適したスレッドで完了イベントが発生します。PostOperationCompleted メソッドが呼び出された後、AsyncOperation のこのインスタンスは使用できなくなります。これ以降、これを使用しようとすると、例外がスローされます。

CompletionMethod シグネチャには、非同期操作の結果を記述するために必要なすべての状態が格納されている必要があります。これには、この特定の非同期操作によってテストされた数値と、数値が素数かどうか、素数でない場合はその最初の除数の値の状態が格納されます。また、発生した例外と、この特定のタスクに対応する AsyncOperation を示す状態も格納されます。

非同期操作を完了するには

  • 完了メソッドを実装します。6 つのパラメータを受け取ります。これらを使用して CalculatePrimeCompletedEventArgs が作成され、クライアントの CalculatePrimeCompletedEventHandler を介してクライアントに返されます。クライアントのタスク ID トークンを内部コレクションから削除し、PostOperationCompleted の呼び出しにより、非同期操作の有効期間を終了します。AsyncOperation は、アプリケーション モデルに適したスレッドまたはコンテキストの呼び出しをマーシャリングします。

    ' 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
    
    // 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 void CompletionMethod(
            int numberToTest,
            int firstDivisor,
            boolean isPrime,
            System.Exception exception,
            boolean canceled,
            AsyncOperation asyncOp)
        {
            // If the task was not previously canceled,
            // remove the task from the lifetime collection.
            if (!canceled)
            {
                synchronized (userStateToLifetime.get_SyncRoot())
                {
                    userStateToLifetime.Remove(asyncOp.get_UserSuppliedState());
                }
            }
    
            // Package the results of the operation in a 
            // CalculatePrimeCompletedEventArgs.
            CalculatePrimeCompletedEventArgs e =
                new CalculatePrimeCompletedEventArgs(
                numberToTest,
                firstDivisor,
                isPrime,
                exception,
                canceled,
                asyncOp.get_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.
    
        }
    
        // 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.
    

チェックポイント

この時点で、コンポーネントをビルドできます。

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

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

    次のコンパイラの警告が表示されます。

    warning CS0169: The private field 'AsynchronousPatternExample.PrimeNumberCalculator.workerDelegate' is never used
    

    この警告は、次のセクションでは解決されます。

ワーカー メソッドの実装

ここまでで、PrimeNumberCalculator コンポーネントをサポートしている非同期コードを実装しました。次に、実際の作業を行うコードを実装します。CalculateWorker、 BuildPrimeNumberList、IsPrime の 3 つのメソッドを実装します。BuildPrimeNumberList と IsPrime は、エラトステネスのふるいと呼ばれる有名なアルゴリズムを構成します。このアルゴリズムは、テスト数値の平方根まですべての素数を検索して、数値が素数かどうかを調べます。その時点で除数が見つからない場合、そのテスト数値は素数です。

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

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

bz33kx67.alert_note(ja-jp,VS.90).gifメモ :

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

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

  1. TaskCanceled ユーティリティ メソッドを実装します。これにより、指定したタスク ID のタスクの有効期間コレクションがチェックされ、タスク ID が見つからなかった場合は、true が返されます。

    ' 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
    
    // 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 boolean TaskCanceled(Object taskId)
        {
            return (!userStateToLifetime.Contains(taskId) );
        }
    
  2. CalculateWorker メソッドを実装します。2 つのパラメータを受け取ります。テストする数値および AsyncOperation です。

    ' 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
    
    // 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);
    }
    
     private void CalculateWorker(
            int numberToTest,
            AsyncOperation asyncOp)
        {
            boolean isPrime = false;
            int firstDivisor = 1;
            System.Exception exception = null;
    
            // Check that the task is still active.
            // The operation may have been canceled before
            // the thread was scheduled.
            if (!TaskCanceled(asyncOp.get_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 (System.Exception ex)
                {
                    exception = ex;
                }
            }
    
            this.CompletionMethod(
                numberToTest,
                firstDivisor,
                isPrime,
                exception,
                TaskCanceled(asyncOp.get_UserSuppliedState()),
                asyncOp);
        }
    
  3. BuildPrimeNumberList を実装します。2 つのパラメータを受け取ります。テストする数値と AsyncOperation です。AsyncOperation を使用して、進行状況およびインクリメンタル結果をレポートします。これにより、クライアントのイベント ハンドラが、アプリケーション モデルに適したスレッドまたはコンテキストで呼び出されるようになります。BuildPrimeNumberList は素数を検出すると、これをインクリメンタル結果として、ProgressChanged イベントのクライアントのイベント ハンドラにレポートします。これには、ProgressChangedEventArgs から派生した CalculatePrimeProgressChangedEventArgs と呼ばれるクラスが必要です。このクラスには、LatestPrimeNumber と呼ばれる追加されたプロパティが 1 つあります。

    また、BuildPrimeNumberList メソッドは、定期的に TaskCanceled メソッドを呼び出し、true が返されると終了します。

    ' 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
    
    // 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 ArrayList BuildPrimeNumberList(int numberToTest, 
            AsyncOperation asyncOp)
        {
            ProgressChangedEventArgs e = null;
            ArrayList primes = new ArrayList();
            int firstDivisor = 1;
            int n = 5;
    
            // Add the first prime numbers.
            primes.Add((Int32)2);
            primes.Add((Int32)3);
    
            // Do the work.
            while (n < numberToTest &&
                   !TaskCanceled( asyncOp.get_UserSuppliedState()) )
            {
                if (IsPrime(primes, n, /** @out */firstDivisor)) 
                {
                    // Report to the client that you found a prime.
                    e = new CalculatePrimeProgressChangedEventArgs(n, 
                        (int)((float)n / ( float)(numberToTest) * 100), 
                        asyncOp.get_UserSuppliedState());
    
                    asyncOp.Post(this.onProgressReportDelegate, e);
    
                    primes.Add((Int32)n);
    
                    // Yield the rest of this time slice.
                    System.Threading.Thread.Sleep(0);
                }
    
                // Skip even numbers.
                n += 2;
    
            }
            return primes;
        } 
    
  4. IsPrime を実装します。3 つのパラメータを受け取ります。既知の素数のリスト、テストする数値、および見つかった最初の除数の出力パラメータです。素数のリストを指定すると、テスト数値が素数かどうかを確認します。

    ' 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
    
    // 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 boolean IsPrime(
            ArrayList primes, 
            int n, 
            /** @ref */int firstDivisor)
        {
            boolean foundDivisor = false;
            boolean 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.get_Count() && !foundDivisor && !exceedsSquareRoot) 
            {
                // The divisor variable will be the smallest 
                // prime number not yet tried.
                divisor = Convert.ToInt32(primes.get_Item(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;
        } 
    
  5. CalculatePrimeProgressChangedEventArgs を ProgressChangedEventArgs から派生させます。このクラスは、ProgressChanged イベントのクライアントのイベント ハンドラに、インクリメンタル結果をレポートするために必要です。LatestPrimeNumber という名前の追加されたプロパティが 1 つあります。

    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
    
    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 
        extends ProgressChangedEventArgs
    {
        private int latestPrimeNumberValue = 1;
    
        public CalculatePrimeProgressChangedEventArgs(int latestPrime, 
            int progressPercentage, Object userToken)
        {
            super(progressPercentage, userToken);
            this.latestPrimeNumberValue = latestPrime;
        }
    
        /** @property 
         */
        public int get_LatestPrimeNumber()
        {
            return latestPrimeNumberValue;
        } 
    }
    

チェックポイント

この時点で、コンポーネントをビルドできます。

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

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

    この他に記述する必要があるものは、非同期操作を開始およびキャンセルするためのメソッド (CalculatePrimeAsync および CancelAsync) です。

開始およびキャンセル メソッドの実装

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

特定の保留中の操作をキャンセルするには、PostOperationCompleted を対応する AsyncOperation で呼び出します。こうするとその操作が終了し、これ以降その 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 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
    
    // 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 void CalculatePrimeAsync(int numberToTest, Object taskId)
        {
            // Create an AsyncOperation for taskId.
            AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(taskId);
            synchronized (userStateToLifetime.get_SyncRoot()) 
            {
                // Multiple threads will access the task dictionary,
                // so it must be locked to serialize access.
                if (userStateToLifetime.Contains(taskId)) {
                    throw new ArgumentException("Task ID parameter must be unique", 
                        "taskId");
                }
    
                userStateToLifetime.set_Item(taskId, asyncOp);
            }
    
            // Start the asynchronous operation.
            WorkerEventHandler workerDelegate = new WorkerEventHandler(CalculateWorker);
            workerDelegate.BeginInvoke(
                numberToTest, 
                asyncOp, 
                null, 
                null);
        } 
    
  2. CancelAsync メソッドを実装します。トークン コレクションに taskId パラメータが存在する場合は、削除されます。これにより、起動されていないキャンセル済みタスクが実行されるのを防止します。タスクが実行中の場合は、そのタスク ID が有効期間コレクションから削除されたことを BuildPrimeNumberList メソッドが検出すると、メソッドは終了します。

    ' 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
    
    // 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 void CancelAsync(Object taskId)
        {
            Object obj = userStateToLifetime.get_Item(taskId);
            if (obj != null)
            {
                synchronized (userStateToLifetime.get_SyncRoot())
                {
                    userStateToLifetime.Remove(taskId);
                }
            }
        }
    

チェックポイント

この時点で、コンポーネントをビルドできます。

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

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

PrimeNumberCalculator コンポーネントがコンパイルされ、使用できるようになります。

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

次の手順

この例に入力するには、CalculatePrimeAsync メソッドの同期用である CalculatePrime を記述します。こうすると、PrimeNumberCalculator コンポーネントがイベント ベースの非同期パターンに完全に準拠します。

この例を改善するには、異なるテスト数値のさまざまな呼び出しによって検出された、すべての素数のリストを保持します。この方法を使用すると、以前のタスクで行われた作業が各タスクに役立ちます。このリストを lock 領域で保護する際には注意が必要です。異なるスレッドによるリストへのアクセスはシリアル化されます。

2、3、5 などの単純な除数をテストしても、この例を改善できます。

参照

処理手順

方法 : バックグラウンドで操作を実行する

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

概念

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

その他の技術情報

Visual Basic におけるマルチスレッド

イベント ベースの非同期パターンを使用したマルチスレッド プログラミング