演练:实现支持基于事件的异步模式的组件

如果您正在编写一个类,其中某些操作可能导致显著延迟,请考虑通过实现 基于事件的异步模式概述 赋予此类异步功能。

此演练介绍如何创建可实现基于事件的异步模式的组件。 它是通过使用 System.ComponentModel 命名空间的帮助器类实现的,这确保了此组件可以在任何应用程序模型(包括 ASP.NET、控制台应用程序和 Windows 窗体应用程序)下正常运行。 您还可以用 PropertyGrid 控件和您自己的自定义设计器设计此组件。

完成后,您将拥有一个可异步计算质数的应用程序。 您的应用程序将有一个主用户界面 (UI) 线程和一个用于各个质数计算的线程。 虽然测试一个较大的数字是否是质数可能需要很长的时间,但是主 UI 线程不会被此延迟中断,并且该窗体在计算期间具有响应能力。 您将能够并行运行任意数量的计算,并能够有选择地取消挂起的计算。

本演练涉及以下任务:

  • 创建组件

  • 定义公共异步事件和委托

  • 定义私有委托

  • 实现公共事件

  • 实现完成方法

  • 实现辅助方法

  • 实现启动和取消方法

若要将本主题中的代码复制为单个列表,请参见如何:实现支持基于事件的异步模式的组件

创建组件

第一步是创建将实现基于事件的异步模式的组件。

创建组件

  • 创建一个名为 PrimeNumberCalculator、从 Component 继承的类。

定义公共异步事件和委托

您的组件使用事件与客户端通信。 异步任务完成后,方法名称Completed 事件会向客户端发出警报,方法名称 ProgressChanged 事件还会通知客户端异步任务的进度。

为您的组件的客户端定义异步事件:

  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 线程上执行的回调方法。 回调方法必须有一个采用 Object 类型的一个参数的签名,这意味着您将需要在包装类中的委托之间传递状态。 有关更多信息,请参见 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. 在组件的构造函数中调用 InitializeDelegates 方法。

    Public Sub New()
    
        InitializeComponent()
    
        InitializeDelegates()
    
    End Sub
    
    public PrimeNumberCalculator()
    {   
        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);
    
  4. 创建一个集合,用来管理挂起的异步操作的生存期。 客户端需要一种方式来跟踪操作的执行和完成情况,此跟踪的实现需要客户端在调用异步方法时传递一个唯一标记或任务 ID。 PrimeNumberCalculator 组件必须通过将任务 ID 与其对应的调用关联起来以跟踪每个调用。 如果客户端传递的任务 ID 不是唯一的,则 PrimeNumberCalculator 组件必须引发一个异常。

    PrimeNumberCalculator 组件通过使用名为 HybridDictionary 的特殊集合类来跟踪任务 ID。 在类定义中,创建一个名为 userTokenToLifetime 的 HybridDictionary

    Private userStateToLifetime As 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);
        }
    }
    

实现完成方法

完成委托是一个方法,在异步操作因成功完成、出现错误或发生取消而结束时,基础的自由线程异步行为会调用此方法。 此调用可在任意线程上发生。

从唯一客户端标记的内部集合中移除客户端任务 ID 就需要使用此方法。 此方法还可以通过在相应的 AsyncOperation 上调用 PostOperationCompleted 方法来结束某个特定异步操作的生存期。 此调用将在适合于应用程序模型的线程上引发完成事件。 调用 PostOperationCompleted 方法后,AsyncOperation 的这一实例不再可用。此后,任何尝试使用此实例的行为都将引发异常。

CompletionMethod 签名必须包含描述异步操作的结果所必需的所有状态。 它包含此特定异步操作所测试的数字的状态,此数是不是质数,如果不是质数,那么它的第一个约数是什么。 它还包含描述任何发生的异常和对应于此特定任务的 AsyncOperation 的状态。

完成异步操作:

  • 实现完成方法。 它采用六个参数;在通过客户端的 CalculatePrimeCompletedEventHandler 返回客户端的 CalculatePrimeCompletedEventArgs 中填充的就是这些参数。 它将客户端的任务 ID 标记从内部集合中移除,然后调用 PostOperationCompleted 结束异步操作的生存期。 AsyncOperation 会将此调用封送到适合于应用程序模型的线程或上下文。

    ' This is the method that the underlying, free-threaded 
    ' asynchronous behavior will invoke.  This will happen on
    '  an arbitrary thread.
    Private 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
    

    此警告将在下一部分中解决。

实现辅助方法

到目前为止,您已为 PrimeNumberCalculator 组件实现了支持的异步代码。 现在,您可以实现执行实际工作的代码。 您将实现三种方法:CalculateWorker、BuildPrimeNumberList 和 IsPrime。 BuildPrimeNumberList 和 IsPrime 共同构成众所周知的称为埃拉托色尼过滤 (Sieve of Eratosthenes) 的算法,此算法通过查找测试数的平方根之内的所有质数来确定一个数是不是质数。 如果没有发现任何约数,则测试数是质数。

如果编写的这个组件要实现最高效率,则它应记住对不同测试数的各种调用所发现的所有质数。 它还会检查最小约数,如 2、3 和 5。 此示例是为了演示如何异步执行耗时的操作,不过,这些优化操作将作为练习留给您来做。

CalculateWorker 方法包装在一个委托中,可通过调用 BeginInvoke 来异步调用此方法。

注意注意

进程报告在 BuildPrimeNumberList 方法中实现。在运行速度快的计算机上,可快速连续引发 ProgressChanged 事件。引发这些事件的客户端线程必须能够处理这种情形。用户界面代码可能会被大量的消息所淹没,无法继续,结果导致挂起行为。有关处理此情形的示例用户界面,请参见如何:实现基于事件的异步模式的客户端

异步执行质数计算:

  1. 实现 TaskCanceled 实用工具方法。 它会检查任务生存期集合中是否存在给定任务 ID,如果没有找到该任务 ID,则返回 true。

    ' Utility method for determining if a 
    ' task has been canceled.
    Private 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。

实现启动和取消方法

通过在包装辅助方法的委托上调用 BeginInvoke 可在其自己的线程上启动该辅助方法。 若要管理特定异步操作的生存期,请调用 AsyncOperationManager 帮助器类上的 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 参数,则会将其移除。 这会阻止未启动的已取消任务运行。 如果该任务正在运行,则当 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 组件的示例客户端,请参见如何:实现基于事件的异步模式的客户端

后续步骤

您可以通过编写 CalculatePrime(CalculatePrimeAsync 方法的同步等效方法)来填写此示例。 这会使 PrimeNumberCalculator 组件完全符合基于事件的异步模式。

您可以通过保留由不同测试数的各种调用所发现的所有质数的列表来改进此示例。 使用此方法,每个任务都会从前面的任务所做的工作中受益。 使用 lock 区域保护此列表时要小心,这样会序列化不同线程对此列表的访问。

您也可以通过测试最小约数(如 2、3 和 5)来改进此示例。

请参见

任务

如何:在后台运行操作

如何:实现支持基于事件的异步模式的组件

概念

基于事件的异步模式概述

其他资源

Multithreading in Visual Basic

使用基于事件的异步模式进行多线程编程