Практическое руководство. Реализация компонента, поддерживающего асинхронную модель на основе событий

Если вы создаете класс и некоторые операции этого класса могут привести к значительным задержкам, подумайте о том, чтобы реализовать для этого класса асинхронные функции с помощью асинхронной модели на основе событий.

В этом руководстве мы покажем, как создать компонент, реализующий асинхронную модель на основе событий. Для такой реализации применяются вспомогательные классы из пространства имен System.ComponentModel, что обеспечивает правильную работу компонента для приложений любой модели, включая ASP.NET, консольные приложения и приложения Windows Forms. Также этот компонент можно создать на основе элемента управления PropertyGrid и пользовательских конструкторов.

Завершив работу с руководством, вы получите приложение, вычисляющее простые числа в асинхронном режиме. В приложении создается главный поток пользовательского интерфейса и отдельный поток для вычисления каждого простого числа. Несмотря на то что проверка крупных простых чисел может занимать много времени, основной поток пользовательского интерфейса не будет прерываться в результате этой задержки, а форма в ходе вычислений будет нормально реагировать на действия пользователя. Вы сможете запустить любое количество вычислений одновременно, а также избирательно отменять еще не оконченные вычисления.

В данном пошаговом руководстве представлены следующие задачи.

  • Создание компонента

  • Определение открытых асинхронных событий и делегатов

  • Определение закрытых делегатов

  • реализация открытых событий;

  • реализация метода завершения;

  • реализация рабочих методов;

  • реализация методов запуска и отмены.

Чтобы скопировать код из этого раздела единым блоком, см. практическое руководство по реализация клиента асинхронной модели на основе событий.

Создание компонента

На первом шаге мы создадим компонент, который будет реализовать асинхронную модель на основе событий.

Создание компонента

  • Создайте класс PrimeNumberCalculator, производный от Component.

Определение открытых асинхронных событий и делегатов

Этот компонент взаимодействует с клиентами с помощью событий. Событие имя_методаCompleted предупреждает клиенты о завершении асинхронной задачи, а событие имя_методаProgressChanged информирует клиенты о ходе выполнения асинхронной задачи.

Чтобы определить асинхронные события для клиентов своего компонента, выполните следующие действия.

  1. Импортируйте пространства имен System.Threading и System.Collections.Specialized в верхней части файла.

    using System;
    using System.Collections;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Globalization;
    using System.Threading;
    using System.Windows.Forms;
    
    Imports System.Collections
    Imports System.Collections.Specialized
    Imports System.ComponentModel
    Imports System.Drawing
    Imports System.Globalization
    Imports System.Threading
    Imports System.Windows.Forms
    
  2. Перед определением класса PrimeNumberCalculator объявите делегаты для событий выполнения и завершения.

    public delegate void ProgressChangedEventHandler(
        ProgressChangedEventArgs e);
    
    public delegate void CalculatePrimeCompletedEventHandler(
        object sender,
        CalculatePrimeCompletedEventArgs e);
    
    Public Delegate Sub ProgressChangedEventHandler( _
        ByVal e As ProgressChangedEventArgs)
    
    Public Delegate Sub CalculatePrimeCompletedEventHandler( _
        ByVal sender As Object, _
        ByVal e As CalculatePrimeCompletedEventArgs)
    
  3. В определении класса PrimeNumberCalculator объявите делегаты для событий информирования клиентов о ходе выполнения и завершении.

    public event ProgressChangedEventHandler ProgressChanged;
    public event CalculatePrimeCompletedEventHandler CalculatePrimeCompleted;
    
    Public Event ProgressChanged _
        As ProgressChangedEventHandler
    Public Event CalculatePrimeCompleted _
        As CalculatePrimeCompletedEventHandler
    
  4. После определения класса PrimeNumberCalculator наследуйте класс CalculatePrimeCompletedEventArgs, чтобы передавать результаты каждого вычисления в обработчик событий клиента для CalculatePrimeCompleted.event. Помимо реализации свойств 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. Объявите и создайте делегаты SendOrPostCallback в классе PrimeNumberCalculator. Создайте объекты SendOrPostCallback во вспомогательном методе с именем InitializeDelegates.

    Вам потребуются два делегата: один для информирования клиента о ходе выполнения, другой — для информирования клиента о завершении.

    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. В определении класса создайте HybridDictionary с именем userStateToLifetime.

    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
    

реализация метода завершения;

Делегат завершения — это метод, который будет вызываться из базовой среды со свободным потоком при успешном завершении, сбое или отмене асинхронной операции. Такой вызов происходит в произвольном потоке.

В этом методе идентификатор клиентской задачи удаляется из внутренней коллекции уникальных клиентских маркеров. Также этот метод завершает время существования конкретной асинхронной операции, вызывая метод PostOperationCompleted для соответствующего объекта AsyncOperation. Этот вызов создает событие завершения в потоке, который соответствует модели приложения. После вызова метода PostOperationCompleted этот экземпляр AsyncOperation нельзя использовать повторно. При любых последующих попытках будет создано исключение.

Подпись CompletionMethod должна содержать все состояния, необходимые для описания результата асинхронной операции. Она содержит информацию о числе, которое проверялось в конкретной асинхронной операции: является ли число простым и каково значение его первого делителя, если число не является простым. Также она содержит состояние, которое описывает все возникшие исключения, и объект AsyncOperation, соответствующий этой конкретной задаче.

Для завершения асинхронной операции сделайте следующее.

  • Реализуйте метод завершения. Он принимает шесть параметров, на основании которых заполняет класс CalculatePrimeCompletedEventArgs и возвращает их клиенту через CalculatePrimeCompletedEventHandler для этого клиента. Он также удаляет маркер идентификатора задачи из внутренней коллекции, после чего завершает срок существования асинхронной операции с помощью вызова 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. Теперь можно переходить к созданию кода, который выполнит фактическую работу. Для этого вы реализуете три метода: CalculateWorker, BuildPrimeNumberList и IsPrime. Сочетание BuildPrimeNumberList и IsPrime реализует хорошо известный алгоритм "решето Эратосфена", который определяет, является ли число простым, проверяя все простые числа меньше квадратного корня из проверяемого числа. Если к этому моменту не будет обнаружено делителей, проверяемое число считается простым.

Если бы этот компонент создавался с прицелом на максимальную эффективность, он должен был бы запоминать все простые числа, обнаруженные при вычислениях для различных проверяемых чисел. Также он выполнял бы проверку на тривиальные делители, например 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. Получите CalculatePrimeProgressChangedEventArgs из ProgressChangedEventArgs. Этот класс используется для передачи результатов по мере их получения в клиентский обработчик событий для события 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

На этом этапе можно выполнить сборку компонента.

Проверка компонента

  • Скомпилируйте компонент.

    Теперь осталось создать только методы CalculatePrimeAsync и CancelAsyncдля запуска и отмены асинхронных операций.

Реализация методов запуска и отмены

Чтобы запустить рабочий метод в отдельном потоке, вызовите BeginInvoke для делегата, который является его оболочкой. Метод CreateOperation вспомогательного класса AsyncOperationManager позволяет управлять временем существования конкретной асинхронной операции. Он возвращает параметр AsyncOperation, который маршалирует вызовы клиентских обработчиков событий в подходящий поток или контекст.

Чтобы отменить незавершенную операцию, вызовите PostOperationCompleted для соответствующего AsyncOperation. Это действие завершает операцию, а все последующие вызовы 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, см. в статье Практическое руководство. Реализация клиента асинхронной модели, основанной на событиях.

Next Steps

Попробуйте изучить следующий пример, в котором вы создадите CalculatePrime, синхронный эквивалент метода CalculatePrimeAsync. После этого компонент PrimeNumberCalculator будет полностью совместим с асинхронной моделью на основе событий.

Вы можете улучшить этот пример, сохранив список всех простых чисел, обнаруженных при вызовах для проверки разных чисел. Это позволит каждой задаче использовать результаты работы всех предыдущих задач. Обязательно защитите этот список с помощью регионов lock, чтобы сериализовать доступ к списку из разных потоков.

Также вы можете улучшить этот пример, выполнив проверку на тривиальные делители, например 2, 3 и 5.

См. также раздел