共用方式為


逐步解說:實作支援事件架構非同步模式的元件

如果您在撰寫的類別具有一些可能造成顯著延遲的作業,請考慮實作事件架構非同步模式概觀以給予該類別非同步的功能。

本逐步解說會說明如何建立實作事件架構非同步模式的元件。 此元件的實作,是使用 System.ComponentModel 命名空間中的 Helper 類別來進行的。如此可確定元件會在任何應用程式模型 (包括 ASP.NET、主控台應用程式和 Windows Form 應用程式) 底下,正確地進行運作。 運用 PropertyGrid 控制項和您自己的自訂控制項,也都能夠設計這個元件。

在完成時,您就會擁有能以非同步方式計算質數值的一個應用程式。 您的應用程式將會具有一個主使用者介面 (UI) 執行緒,以及用於每次質數計算的另一個執行緒。 儘管測試大型數字是否為質數需要一些時間,主 UI 執行緒依然不會被這項延遲中斷,而且在進行計算時,表單也會有回應的能力。 您將能夠盡量執行並行計算,並能選擇性地取消擱置的計算。

逐步解說將說明的工作包括:

  • 建立元件

  • 定義公用非同步事件和委派 (Delegate)

  • 定義私用委派

  • 實作公用事件

  • 實作 Completion 方法

  • 實作 Worker 方法

  • 實作 Start 和 Cancel 方法

若要將此主題中的程式碼複製為一份清單,請參閱 HOW TO:實作支援事件架構非同步模式的元件

建立元件

第一個步驟就是建立將會實作事件架構非同步模式的元件。

若要建立元件

  • 建立繼承自 Component、稱為 PrimeNumberCalculator 的類別。

定義公用非同步事件和委派

元件會使用事件來與用戶端通訊。 MethodNameCompleted 事件會向用戶端警示非同步工作的完成,而 MethodNameProgressChanged 事件則會向用戶端通知非同步工作的進度。

若要為元件的用戶端定義非同步事件:

  1. 在檔案的頂端,匯入 System.ThreadingSystem.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;
    
  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);
    
  3. 在 PrimeNumberCalculator 類別定義中,宣告事件以對用戶端報告進度和完成。

    Public Event ProgressChanged _
        As ProgressChangedEventHandler
    Public Event CalculatePrimeCompleted _
        As CalculatePrimeCompletedEventHandler
    
    public event ProgressChangedEventHandler ProgressChanged;
    public event CalculatePrimeCompletedEventHandler CalculatePrimeCompleted;
    
  4. 在 PrimeNumberCalculator 類別定義之後,衍生 CalculatePrimeCompletedEventArgs 類別,以便將每次計算的結果,都報告到 CalculatePrimeCompleted 事件的用戶端事件處理常式。 除了 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;
                }
            }
        }
    
    

檢查點

完成這個步驟之後,您就可以建置元件了。

若要測試您的元件

  • 編譯元件。

    您將會收到兩項編譯器警告:

    warning CS0067: The event 'AsynchronousPatternExample.PrimeNumberCalculator.ProgressChanged' is never used
    warning CS0067: The event 'AsynchronousPatternExample.PrimeNumberCalculator.CalculatePrimeCompleted' is never used
    

    這些警告將會在下一區段中清除。

定義私用委派

PrimeNumberCalculator 元件的非同步概念,是由稱為 SendOrPostCallback 的特殊委派,在內部進行實作的。 SendOrPostCallback 表示在 ThreadPool 執行緒上所執行的回呼 (Callback) 方法。 此回呼方法必須具有使用 Object 型別之單一參數的簽章 (Signature),這表示您將需要在包裝函式類別 (Wrapper Class) 中的委派之中傳遞狀態。 如需詳細資訊,請參閱 SendOrPostCallback

若要實作元件的內部非同步行為:

  1. 在 PrimeNumberCalculator 類別中,宣告和建立 SendOrPostCallback 委派。 在稱為 InitializeDelegates 的公用程式方法中,建立 SendOrPostCallback 物件。

    您將需要兩個委派:一個用於向用戶端回報進度,另一個則用於向用戶端回報已完成作業。

    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);
    }
    
  2. 在您的元件建構函式 (Constructor) 內呼叫 InitializeDelegates 方法。

    Public Sub New()
    
        InitializeComponent()
    
        InitializeDelegates()
    
    End Sub
    
    public PrimeNumberCalculator()
    {   
        InitializeComponent();
    
        InitializeDelegates();
    }
    
  3. 在處理需要非同步地完成之實際工作的 PrimeNumberCalculator 類別中,宣告一個委派。 這個委派會包裝測試數值是否為質數的 Worker 方法。 這個委派會使用 AsyncOperation 參數來追蹤非同步作業的存留期 (Lifetime)。

    Private Delegate Sub WorkerEventHandler( _
    ByVal numberToCheck As Integer, _
    ByVal asyncOp As AsyncOperation)
    
    private delegate void WorkerEventHandler(
        int numberToCheck,
        AsyncOperation asyncOp);
    
  4. 為擱置中非同步運算建立管理存留期的集合。 在作業執行和完成時,用戶端需要具有追蹤這些作業的方法。若要進行這種追蹤,需要用戶端在對非同步方法進行呼叫時,傳遞唯一語彙基元 (Token,又稱為工作 ID)。 PrimeNumberCalculator 元件必須使工作 ID 與其對應的引動過程關聯,以持續追蹤每次呼叫。 如果用戶端傳遞的工作 ID 並非唯一,PrimeNumberCalculator 元件就必須引發例外狀況。

    PrimeNumberCalculator 元件會使用稱為 HybridDictionary 的特殊集合類別,持續追蹤工作 ID。 在類別定義中,建立一個稱為 userTokenToLifetime 的 HybridDictionary

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

實作公用事件

實作事件架構非同步模式的元件,都會使用事件來與用戶端通訊。 藉由 AsyncOperation 類別的協助,這些事件會在適當的執行緒上被叫用 (Invoke)。

若要對元件的用戶端引發事件:

  • 實作公用事件以向用戶端報告。 您將需要一個用來報告進度的事件,以及一個用來報告完成的事件。

    ' 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);
        }
    }
    

實作 Completion 方法

完成委派就是在非同步作業由於成功完成、錯誤或取消而結束時,便會由基礎、無限制執行緒的非同步行為所叫用的方法。 這個引動過程會在任意執行緒上發生。

這個方法,也就是從唯一用戶端語彙基元的內部集合中,移除用戶端工作 ID 的地方。 這個方法也會呼叫對應 AsyncOperation 上的 PostOperationCompleted 方法,以結束特定非同步作業的存留期。 這個呼叫會在適合於應用程式模型的執行緒上,引發完成事件。 在呼叫 PostOperationCompleted 方法之後,就會無法再使用這個 AsyncOperation 執行個體,而且使用該執行個體的任何嘗試,都會擲回例外狀況。

CompletionMethod 簽章必須持有說明非同步作業結果的所有狀態。 此類別持有這個特定非同步作業所測試之數值的狀態:數值是否為質數,以及不為質數時所具有的第一個除數。 此類別也持有用來描述發生之任何例外狀況及對應於此特定工作之 AsyncOperation 的狀態。

若要完成非同步作業:

  • 實作完成方法。 這個方法會使用六個參數,以填入將會透過用戶端的 CalculatePrimeCompletedEventHandler 傳回給用戶端的 CalculatePrimeCompletedEventArgs。 此方法還會從內部集合中移除用戶端的工作 ID 語彙基元,並以對於 PostOperationCompleted 的呼叫來結束非同步作業的存留期。 AsyncOperation 則會將此呼叫封送處理 (Marshal) 至執行緒,或是適合於應用程式模型的內容。

    ' 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.
    }
    

檢查點

完成這個步驟之後,您就可以建置元件了。

若要測試您的元件

  • 編譯元件。

    您將會收到一項編譯器警告:

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

    這些警告將會在下一區段中解決。

實作 Worker 方法

直到目前為止,您已經實作了 PrimeNumberCalculator 元件的支援非同步程式碼。 現在您就可以實作進行實際工作的程式碼。 您將會實作三種方法 CalculateWorker、 BuildPrimeNumberList 和 IsPrime。 在一起使用時,BuildPrimeNumberList 和 IsPrime 組成了稱為 Sieve of Eratosthenes 的著名演算式,此演算式會找出直到測試數值平方根的所有質數,以判斷該數值是否為質數。 如果到時候都沒有找到任何除數,則表示測試數值就是質數。

如果這個元件的撰寫是為了提供最高效率,就會記得針對不同測試數值的各種引動過程所探索到的所有質數。 此元件也會檢查 2、3、5 之類的普通除數。 然而,這個範例的目的是要示範非同步作業的執行會如何耗費時間,因此這些最佳化的事項都將留給您做為練習。

CalculateWorker 方法包裝在委派中,而且會以 BeginInvoke 的呼叫來非同步叫用。

注意事項注意事項

進度回報會在 BuildPrimeNumberList 方法中實作。在速度快的電腦中,可以在快速執行時引發 ProgressChanged 事件。用戶端執行緒 (即引發這些事件的執行緒) 必須能夠處理這個情況。使用者介面的程式碼可能充滿了訊息且無法保留,導致懸置行為的產生。如需處理這個情況的使用者介面範例,請參閱 HOW TO:實作事件架構非同步模式的用戶端

若要非同步地執行質數計算:

  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 );
    }
    
  2. 實作 CalculateWorker 方法。 這項實作需要兩個參數:要測試的數值,以及 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);
    }
    
  3. 實作 BuildPrimeNumberList。 這項實作需要使用兩個參數:要測試的數值,以及 AsyncOperation。 此實作會使用 AsyncOperation 來報告進度和累加結果。 如此就能確定用戶端的事件處理常式,會在適當的執行緒或應用程式模型的內容中被呼叫。 在 BuildPrimeNumberList 找到質數時,就會將此報告為 ProgressChanged 事件之用戶端事件處理常式的累加結果。 此時需要使用衍生自 ProgressChangedEventArgs 的類別 (稱為 CalculatePrimeProgressChangedEventArgs),此類別包含一個添加的屬性,稱為 LatestPrimeNumber。

    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;
    }
    
  4. 實作 IsPrime。 這項實作需要使用三個參數:一份已知質數的清單、要測試的數值,以及找到的第一個除數的輸出參數。 利用質數的清單,此實作即可判斷測試數值是否為質數。

    ' 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;
    }
    
  5. ProgressChangedEventArgs 衍生 CalculatePrimeProgressChangedEventArgs。 要向 ProgressChanged 事件的用戶端事件處理常式報告累加結果,這個類別是必要的。 此類別有一個稱為 LatestPrimeNumber 的添加屬性。

    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;
            }
        }
    }
    

檢查點

完成這個步驟之後,您就可以建置元件了。

若要測試您的元件

  • 編譯元件。

    還需要撰寫的部分,只剩下啟動和取消非同步作業的方法,也就是 CalculatePrimeAsync 和 CancelAsync。

實作 Start 和 Cancel 方法

在包裝 Worker 方法的委派上呼叫 BeginInvoke,即可讓 Worker 方法在自己的執行緒上啟動。 若要管理特定非同步作業的存留期,您可以呼叫 AsyncOperationManager Helper 類別上的 CreateOperation 方法。 這樣就會傳回 AsyncOperation,它會將用戶端事件處理常式的呼叫封送處理至適當的執行緒或內容。

藉由在其對應的 AsyncOperation 上呼叫 PostOperationCompleted,即可取消特定的擱置中的作業。 這樣就會結束作業,而且對於 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);
    }
    
  2. 實作 CancelAsync 方法。 如果 taskId 參數存在於語彙基元 (Token) 集合內,則會被移除。 如此一來,可避免執行尚未啟動的取消工作。 如果該工作正在執行中,則當 BuildPrimeNumberList 方法偵測到此工作 ID 已從存留期集合中移除時,就會結束。

    ' 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);
            }
        }
    }
    

檢查點

完成這個步驟之後,您就可以建置元件了。

若要測試您的元件

  • 編譯元件。

PrimeNumberCalculator 元件現在已經完成,並且已經可以使用。

如需使用 PrimeNumberCalculator 元件的範例用戶端,請參閱 HOW TO:實作事件架構非同步模式的用戶端

後續步驟

您可以藉由撰寫 CalculatePrime,也就是 CalculatePrimeAsync 方法的同步對等方法,來填寫這個範例。 這樣就會讓 PrimeNumberCalculator 元件完全遵循事件架構非同步模式。

保留針對不同測試數值的各種引動過程所探索到所有質數清單,即可改進這個範例。 只要使用這種處理方法,每項工作都會受益於先前完成的工作。 請以 lock 區域小心保護這份清單,如此即可序列化不同執行緒對此清單的存取。

您也可以測試普通除數 (像是 2、3 和 5),以改進這個範例。

請參閱

工作

HOW TO:在背景執行作業

HOW TO:實作支援事件架構非同步模式的元件

概念

事件架構非同步模式概觀

其他資源

Multithreading in Visual Basic

使用事件架構非同步模式設計多執行緒程式