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

如果您正在撰寫的類別含有一些可能造成明顯延遲的作業,請考慮實作事件架構非同步模式概觀,來為它提供非同步功能。

本逐步解說說明如何建立實作「事件架構非同步模式」的元件。 其實作方式是使用 System.ComponentModel 命名空間中的協助程式類別,確保此元件在任何應用程式模型下都能正常運作,包括 ASP.NET、主控台應用程式及 Windows Forms 應用程式。 您也可以使用 PropertyGrid 控制項和您自己的自訂設計工具來設計此元件。

完成時,您將擁有一個以非同步方式計算質數的應用程式。 您的應用程式將會有一個主要使用者介面 (UI) 執行緒,以及一個用來計算每個質數的執行緒。 雖然測試一個大數是否為質數所需的時間很長,但主要 UI 執行緒並不會被此延遲打斷,在計算期間表單將能持續回應。 您將能夠想要執行多少次計算就同時執行多少次計算,並選擇性地取消擱置中的計算。

這個逐步解說中所述的工作包括:

  • 建立元件

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

  • 定義私用委派

  • 實作公用事件

  • 實作完成方法

  • 實作背景工作方法

  • 實作開始和取消方法

若要將本主題中的程式碼複製成單一清單,請參閱如何:實作事件架構非同步模式的用戶端

建立元件

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

建立元件

  • 建立一個名為 PrimeNumberCalculator 且會從 Component 繼承的類別。

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

您的元件會使用事件與用戶端進行通訊。 MethodNameCompleted 事件會對用戶端發出已完成非同步工作的警示,而 MethodNameProgressChanged 事件則是會通知用戶端非同步工作的進度。

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

  1. 在您檔案的頂端匯入 System.ThreadingSystem.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

現在,您可以建置元件。

測試元件

  • 編譯元件。

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

    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 物件。

    您將需要兩個委派:一個用來向用戶端回報進度,另一個用來向用戶端回報完成。

    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. 建立一個用來管理擱置中非同步作業的集合。 用戶端需要一個可追蹤作業執行和完成狀態的方式,而此追蹤的執行方式是在用戶端對非同步方法發出呼叫時,要求用戶端傳遞唯一的權杖或工作識別碼。 PrimeNumberCalculator 元件必須將工作識別碼與其對應的引動過程建立關聯,來追蹤每個呼叫。 如果用戶端傳遞的工作識別碼不是唯一的,PrimeNumberCalculator 元件必須引發例外狀況。

    PrimeNumberCalculator 元件會使用一個名為 HybridDictionary 的特殊集合類別來追蹤工作識別碼。 在類別定義中,建立名為 userStateToLifetimeHybridDictionary

    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
    

實作完成方法

完成委派是當非同步作業以成功完成、發生錯誤或取消作為結束時,基礎無限制執行緒非同步行為將叫用的方法。 此引動過程會在任意的執行緒上發生。

從唯一用戶端權杖的內部集合移除用戶端工作識別碼時,就是在此方法中進行。 此方法也會藉由呼叫對應之 AsyncOperation 上的 PostOperationCompleted 方法,來結束特定非同步作業的存留期。 此呼叫會在適用於應用程式模型的執行緒上引發完成事件。 在呼叫 PostOperationCompleted 方法之後,即無法再使用這個 AsyncOperation 執行個體,後續所有嘗試使用它的行為都會擲回例外狀況。

CompletionMethod 簽章必須保存描述非同步作業結果所需的所有狀態。 它會保存此特定非同步作業所測試的數字、該數字是否為質數,以及該數字第一個除數的值 (如果該數字不是質數的話)。 它也會保存描述所發生之任何例外狀況的狀態,以及與此特定工作對應的 AsyncOperation

完成非同步作業:

  • 實作完成方法。 此方法採用六個參數,用來填入會透過用戶端的 CalculatePrimeCompletedEventHandler.傳回到用戶端的 CalculatePrimeCompletedEventArgs。 它會從內部集合移除用戶端的工作識別碼權杖,然後藉由對 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

現在,您可以建置元件。

測試元件

  • 編譯元件。

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

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

    在下一節中將會解決此警告。

實作背景工作方法

到目前為止,您已經為 PrimeNumberCalculator 元件實作提供支援的非同步程式碼。 現在,您可以實作執行實際工作的程式碼。 您將實作三個方法:CalculateWorkerBuildPrimeNumberListIsPrimeBuildPrimeNumberListIsPrime 一起會構成稱為「埃拉托斯特尼篩法」(Sieve of Eratosthenes) 的著名演算法,此演算法可藉由尋找所有質數,最大可到測試數字的平方根為止,來判斷數字是否為質數。 如果到該點為止未發現任何除數,即表示該測試數字為質數。

如果此元件的撰寫目的是要發揮最高效率,它就會記住不同測試數字的各種引動過程所探索到的所有質數。 此外,它也會檢查是否有平凡除數,例如 2、3 及 5。 此範例的目的是要示範如何以非同步方式執行耗時的作業,不過,也因此這些最佳化會留給您作為練習。

CalculateWorker 方法會包裝在委派中,且叫用此方法時,會藉由對 BeginInvoke 的呼叫以非同步方式叫用。

注意

實作進度回報時,是在 BuildPrimeNumberList 方法中實作。 在執行速度快的電腦上,可以快速地連續引發 ProgressChanged 事件。 作為這些事件之引發位置的用戶端執行緒必須能夠處理此情況。 使用者介面程式碼可能會湧入大量訊息而來不及處理,導致停止回應。 如需可處理此情況的範例使用者介面,請參閱如何:實作事件架構非同步模式的用戶端

以非同步方式執行質數計算:

  1. 實作 TaskCanceled 公用程式方法。 這會檢查指定之工作識別碼的工作存留期集合,如果找不到該工作識別碼,就會傳回 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

    // 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。 它會使用 AsyncOperation 來回報進度和累加結果。 這可確保在應用程式模型的適當執行緒或內容上呼叫用戶端的事件處理常式。 當 BuildPrimeNumberList 找到質數時,它會以累加結果的方式,向 ProgressChanged 事件的用戶端事件處理常式回報此質數。 這需要一個衍生自 ProgressChangedEventArgs 的類別 (稱為 CalculatePrimeProgressChangedEventArgs),此類別具有一個稱為 LatestPrimeNumber 的附加屬性。

    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。 此方法採用三個參數:已知的質數清單、要測試的數字,以及所找到之第一個除數的輸出參數。 在有質數清單的情況下,它會判斷測試數字是否為質數。

    // 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的附加屬性。

    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 以將用戶端事件處理常式上的呼叫封送處理至適當的執行緒或內容。

取消特定擱置中作業的方式,是呼叫其對應之 AsyncOperation 上的 PostOperationCompleted。 這會結束該作業,後續對其 AsyncOperation 發出的呼叫都會擲回例外狀況。

實作「開始」和「取消」功能:

  1. 實作 CalculatePrimeAsync 方法。 請確定就代表目前擱置中工作的所有權杖而言,用戶端所提供的權杖 (工作識別碼) 是唯一的。 如果用戶端傳入的權杖不是唯一的,CalculatePrimeAsync 就會引發例外狀況。 否則,該權杖會新增至工作識別碼集合中。

    // 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 方法就會結束。

    // 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 元件的範例用戶端,請參閱如何:實作事件架構非同步模式的用戶端

後續步驟

您可以撰寫 CalculatePrime (CalculatePrimeAsync 方法的對等同步方法) 來填寫此範例。 這將可讓 PrimeNumberCalculator 元件完全符合「事件架構非同步模式」的規範。

您可以藉由保留不同測試數字的各種引動過程所探索到的所有質數清單,來改善此範例。 使用此做法時,每個工作都可從先前工作所完成的工作受益。 請使用 lock 區域來小心保護此清單,如此才能將不同執行緒對此清單的存取序列化。

您也可以針對平凡除數 (例如 2、3 及 5) 進行測試來改善此範例。

另請參閱