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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Чтобы создать компонент

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

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

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

Чтобы определить асинхронные события для клиентов пользовательского компонента:

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

    Imports System
    Imports System.Collections
    Imports System.Collections.Specialized
    Imports System.ComponentModel
    Imports System.Drawing
    Imports System.Globalization
    Imports System.Threading
    Imports System.Windows.Forms
    
    using System;
    using System.Collections;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Globalization;
    using System.Threading;
    using System.Windows.Forms;
    
  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. Объявите и создайте делегаты SendOrPostCallback в классе PrimeNumberCalculator . Создайте объекты SendOrPostCallback в служебном методе InitializeDelegates.

    Будут необходимы два делегата: один для отображения клиенту хода выполнения, а другой — для оповещения клиента о завершении.

    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. Создайте коллекцию для управления жизненными циклами асинхронных операций в очереди. Клиент нуждается в способе отслеживания приложений по мере их выполнения и завершения. Это отслеживание реализуется путем обязательной передачи клиентом уникального маркера (или идентификатора задачи) при вызове клиентом асинхронного метода. Компонент PrimeNumberCalculator должен отслеживать каждый вызов путем сопоставления идентификатора задачи с соответствующим вызовом. Если клиент передает идентификатор задачи, который не является уникальным, компонент PrimeNumberCalculator должен создать исключение.

    Компонент PrimeNumberCalculator отслеживает идентификаторы задач с помощью специального класса коллекции, который называется HybridDictionary. В определении класса создайте объект HybridDictionary с названием userTokenToLifetime.

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

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

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

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

Чтобы сделать этот компонент максимально эффективным, в нем должны запоминаться все основные числа, обнаруженные во время различных вызовов для проверки различных чисел. Также будут проверены простые делители, такие как 2, 3 и 5. Предназначение этого примера заключается в необходимости демонстрации асинхронного выполнения операций, которые занимают много времени, поэтому поупражняйтесь в оптимизации самостоятельно.

Метод CalculateWorker заключается в оболочку делегата и вызывается асинхронно при вызове BeginInvoke.

ПримечаниеПримечание

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

Чтобы асинхронно выполнить вычисление простого числа:

  1. Реализуйте рабочий метод TaskCanceled. Это приведет к проверке коллекции жизненных циклов задач для определенного идентификатора задачи и возвратит значение 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. Сделайте CalculatePrimeProgressChangedEventArgs производным из ProgressChangedEventArgs. Этот класс необходим для предоставления добавочных результатов клиентскому обработчику события 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 в делегате, который является для него оболочкой. Чтобы управлять жизненным циклом определенной асинхронной операции, следует вызвать метод 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 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 завершает работу при определении, что идентификатор задачи был удален из коллекции жизненных циклов.

    ' 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

Многопоточное программирование с использованием асинхронной модели, основанной на событиях