Como: Implementar um componente compatível com o padrão assíncrono baseado em evento

Se você estiver escrevendo uma classe com algumas operações que possam causar atrasos notáveis, considere a opção de fornecer funcionalidade assíncrona Implementando a visão geral de padrão assíncrono baseado em evento.

Este passo a passo ilustra como criar um componente que implemente o Padrão Assíncrono Baseado em Evento. Ele é implementado usando classes auxiliares do namespace System.ComponentModel, o que garante que o componente funcione corretamente em qualquer modelo de aplicativo, incluindo ASP.NET, aplicativos do Windows Forms e aplicativos de Console. Esse componente também é projetável com um controle PropertyGrid e seus próprios designers personalizados.

Quando terminar, você terá um aplicativo que calcula números primos de forma assíncrona. O aplicativo terá um thread de IU (interface do usuário) do usuário principal e um thread para cada cálculo de números primos. Embora o teste para saber se um número grande é primo possa levar um tempo considerável, o thread de interface do usuário principal não será interrompido por esse atraso e o formulário responderá durante o cálculo. Você poderá executar tantos cálculos quantos desejar simultaneamente e cancelar seletivamente cálculos pendentes.

As tarefas ilustradas neste passo a passo incluem:

  • Criando o componente

  • Definir representantes e eventos assíncronos públicos

  • Definir representantes privados

  • Implementar eventos públicos

  • Implementar o método de conclusão

  • Implementar os métodos de trabalhador

  • Implementar métodos Iniciar e Cancelar

Para copiar o código deste tópico como uma única lista, confira Como implementar um cliente do Padrão assíncrono baseado em evento.

Criando o componente

A primeira etapa é criar o componente que implementará o Padrão Assíncrono Baseado em Evento.

Para criar o componente

  • Criar uma classe chamada PrimeNumberCalculator que herda de Component.

Definir representantes e eventos assíncronos públicos

O componente se comunica com clientes usando eventos. O evento MethodNameCompleted alerta os clientes da conclusão de uma tarefa assíncrona, e o evento MethodNameProgressChanged informa os clientes do progresso de uma tarefa assíncrona.

Para definir eventos assíncronos para clientes do componente:

  1. Importe os namespaces System.Threading e System.Collections.Specialized na parte superior do arquivo.

    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. Antes da definição de classe PrimeNumberCalculator, declare representantes para eventos de andamento e conclusão.

    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. Na definição de classe PrimeNumberCalculator, declare eventos para relatar o andamento e a conclusão aos clientes.

    public event ProgressChangedEventHandler ProgressChanged;
    public event CalculatePrimeCompletedEventHandler CalculatePrimeCompleted;
    
    Public Event ProgressChanged _
        As ProgressChangedEventHandler
    Public Event CalculatePrimeCompleted _
        As CalculatePrimeCompletedEventHandler
    
  4. Após a definição de classe PrimeNumberCalculator, derive a classe CalculatePrimeCompletedEventArgs para relatar o resultado de cada cálculo ao manipulador de eventos do cliente para o evento CalculatePrimeCompleted. Além das propriedades AsyncCompletedEventArgs, essa classe permite que o cliente determine qual número foi testado, se ele é primo e qual é o primeiro divisor, se não for primo.

    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
    

Ponto de verificação 1

Neste ponto, você pode compilar o componente.

Para testar o componente

  • Compile o componente.

    Você receberá dois avisos do compilador:

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

    Esses avisos serão removidos na próxima seção.

Definir representantes privados

Os aspectos assíncronos do componente PrimeNumberCalculator são implementados internamente com um representante especial conhecido como SendOrPostCallback. Um SendOrPostCallback representa um método de retorno de chamada que é executado em um thread ThreadPool. O método de retorno de chamada deve ter uma assinatura que use um único parâmetro do tipo Object, o que significa que você precisará passar o estado entre representantes em uma classe wrapper. Para obter mais informações, consulte SendOrPostCallback.

Para implementar o comportamento assíncrono interno do componente:

  1. Declare e crie os representantes SendOrPostCallback na classe PrimeNumberCalculator. Crie os objetos SendOrPostCallback em um método de utilitário chamado InitializeDelegates.

    Você precisará de dois representantes: uma para relatar o andamento ao cliente e outro para relatar a conclusão ao cliente.

    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. Chame o método InitializeDelegates no construtor do componente.

    public PrimeNumberCalculator()
    {
        InitializeComponent();
    
        InitializeDelegates();
    }
    
    Public Sub New()
    
        InitializeComponent()
    
        InitializeDelegates()
    
    End Sub
    
  3. Declare um representante na classe PrimeNumberCalculator que manipula o trabalho real para ser executado de forma assíncrona. Esse representante encapsula o método de trabalho que verifica se um número é primo. O representante utiliza um parâmetro AsyncOperation, que será usado para acompanhar o tempo de vida da operação assíncrona.

    private delegate void WorkerEventHandler(
        int numberToCheck,
        AsyncOperation asyncOp);
    
    Private Delegate Sub WorkerEventHandler( _
    ByVal numberToCheck As Integer, _
    ByVal asyncOp As AsyncOperation)
    
  4. Crie um conjunto para gerenciar os tempos de vida de operações assíncronas pendentes. O cliente precisa de uma maneira para rastrear as operações à medida que elas são executadas e concluídas. Esse acompanhamento é feito exigindo que o cliente passe um token exclusivo, ou ID de tarefa, quando o cliente faz a chamada ao método assíncrono. O componente PrimeNumberCalculator deve manter o controle de cada chamada associando a ID da tarefa a sua invocação correspondente. Se o cliente passar uma ID de tarefa que não seja exclusiva, o componente PrimeNumberCalculator deverá gerar uma exceção.

    O componente PrimeNumberCalculator mantém o controle de ID da tarefa usando uma classe de coleção especial chamada HybridDictionary. Na definição de classe, crie um HybridDictionary chamado userStateToLifetime.

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

Implementar eventos públicos

Os componentes que implementam o Padrão Assíncrono baseado em Evento se comunicam com clientes usando os eventos. Esses eventos são invocados no thread adequado com a ajuda da classe AsyncOperation.

Para gerar eventos para clientes do componente:

  1. Implemente eventos públicos para relatórios para os clientes. Você precisará de um evento para o relatório de andamento e de outro para o relatório de conclusão.

    // 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
    

Implementar o método de conclusão

O representante de conclusão é o método que o comportamento assíncrono subjacente independente de thread chamará quando a operação assíncrona terminar como bem-sucedida, com erro ou cancelamento. Essa chamada ocorre em um thread arbitrário.

Esse método é onde a ID da tarefa do cliente é removida da coleção interna de tokens de cliente exclusivos. Esse método também termina o tempo de vida de determinada operação assíncrona chamando o método PostOperationCompleted no AsyncOperation correspondente. Essa chamada gera o evento de conclusão no thread que é apropriado para o modelo de aplicativo. Após o método PostOperationCompleted ser chamado, essa instância do AsyncOperation não pode mais ser usada, e quaisquer tentativas subsequentes de usá-lo gerarão uma exceção.

A assinatura CompletionMethod deve conter todos os estados necessárias para descrever o resultado da operação assíncrona. Mantém o estado para o número que foi testado por essa operação assíncrona específica, indica se o número é primo e o fornece valor do primeiro divisor, caso não seja um número primo. Também contém o estado que descreve qualquer exceção que ocorreu e o AsyncOperation correspondente a essa tarefa específica.

Para concluir uma operação assíncrona:

  • Implemente o método de conclusão. Utiliza seis parâmetros, que são usados para popular um CalculatePrimeCompletedEventArgs que é retornado ao cliente por meio do CalculatePrimeCompletedEventHandler desse cliente. Remove o token de ID de tarefa do cliente da coleção interna e termina o tempo de vida da operação assíncrona com uma chamada para PostOperationCompleted. O AsyncOperation controla a chamada para o thread ou o contexto apropriado para o modelo de aplicativo.

    // 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
    

Ponto de verificação 2

Neste ponto, você pode compilar o componente.

Para testar o componente

  • Compile o componente.

    Você receberá um aviso do compilador:

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

    Esse aviso será resolvido na próxima seção.

Implementar os métodos de trabalhador

Até agora, você implementou o código de suporte assíncrono para o componente PrimeNumberCalculator. Agora você pode implementar o código que faz o trabalho real. Você implementará os três métodos: CalculateWorker, BuildPrimeNumberList e IsPrime. Juntos, BuildPrimeNumberList e IsPrime compõem um algoritmo bem conhecido chamado Sieve de Eratosthenes, que determina se um número é primo localizando todos os números primos até a raiz quadrada do número de teste. Se nenhum divisor for encontrado até esse ponto, o número de teste será primo.

Se esse componente fosse desenvolvido para máxima eficiência, se lembraria de todos os números primos descobertos por várias invocações de diferentes números de teste. Também deve verificar se há divisores triviais como 2, 3 e 5. A intenção desse exemplo é demonstrar como operações demoradas podem ser executadas de forma assíncrona, no entanto. Assim, essas otimizações são deixadas como um exercício para você.

O método CalculateWorker é encapsulado em um representante e é invocado de forma assíncrona com uma chamada para BeginInvoke.

Observação

O relatório de progresso é implementado no método BuildPrimeNumberList. Em computadores rápidos, eventos ProgressChanged podem ser gerados em sucessão rápida. O thread de cliente, em que esses eventos são gerados, deve ser capaz de lidar com essa situação. O código de interface do usuário pode ser inundado com mensagens e impossível de acompanhar, o que resulta em falta de resposta. Para uma interface de usuário de exemplo que manipula essa situação, confira Como implementar um cliente do padrão assíncrono baseado em evento.

Para executar o cálculo de número primo de forma assíncrona:

  1. Implementar o método de utilitário TaskCanceled. Isso verifica a coleção de tempo de vida de tarefa para a ID de tarefa específica e retorna true se a ID da tarefa não é encontrada.

    // 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. Implementar o método de CalculateWorker . Usa dois parâmetros: um número a ser testado e um 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. Implementar BuildPrimeNumberList. Usa dois parâmetros: o número a ser testado e um AsyncOperation. Usa o AsyncOperation para relatar o andamento e os resultados incrementais. Isso garante que os manipuladores de eventos do cliente sejam chamados no thread ou contexto adequado para o modelo de aplicativo. Quando BuildPrimeNumberList localiza um número primo, o relata como um resultado incremental para o manipulador de eventos do cliente para o evento ProgressChanged. Isso requer uma classe derivada de ProgressChangedEventArgs, chamada CalculatePrimeProgressChangedEventArgs, que tem uma propriedade adicionada chamada LatestPrimeNumber.

    O método BuildPrimeNumberList também chama o método TaskCanceled e sai se o método retorna 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. Implementar IsPrime. Usa três parâmetros: uma lista de números primos conhecidos, o número a ser testado e um parâmetro de saída para o primeiro divisor encontrado. Dada a lista de números primos, determina se o número de teste é primo.

    // 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. Derive CalculatePrimeProgressChangedEventArgs de ProgressChangedEventArgs. Essa classe é necessária para relatar resultados incrementais ao manipulador de eventos do cliente para o evento ProgressChanged. Tem uma propriedade adicional chamada 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
    

Ponto de verificação 3

Neste ponto, você pode compilar o componente.

Para testar o componente

  • Compile o componente.

    Tudo o que resta a serem gravado são os métodos para iniciar e cancelar operações assíncronas, CalculatePrimeAsync e CancelAsync.

Implementar os métodos Iniciar e Cancelar

Você inicia o método de trabalho em seu próprio thread chamando BeginInvoke no representante que o encapsula. Para gerenciar o tempo de vida de determinada operação assíncrona, você deve chamar o método CreateOperation na classe auxiliar AsyncOperationManager. Isso retorna um AsyncOperation, que realiza marshaling de chamadas nos manipuladores de eventos do cliente para o contexto ou thread adequado.

Você cancela determinada operação pendente chamando PostOperationCompleted em seu AsyncOperation correspondente. Isso encerra a operação, e qualquer chamada subsequente para seu AsyncOperation gerará uma exceção.

Para implementar a funcionalidade de Iniciar e Cancelar:

  1. Implementar o método de CalculatePrimeAsync . Verifique se o token fornecido pelo cliente (ID da tarefa) é exclusivo em relação a todos os tokens que representam tarefas pendentes atualmente. Se o cliente passar um token não exclusivo, CalculatePrimeAsync gerará uma exceção. Caso contrário, o token será adicionado à coleção de ID de tarefa.

    // 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. Implementar o método de CancelAsync . Se o parâmetro taskId existir na coleção de token, será removido. Isso impede que tarefas canceladas que não foram iniciadas sejam executadas. Se a tarefa estiver em execução, o método BuildPrimeNumberList será encerrado quando detectar que a ID da tarefa foi removida da coleção de tempo de vida.

    // 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
    

Ponto de verificação 4

Neste ponto, você pode compilar o componente.

Para testar o componente

  • Compile o componente.

O componente PrimeNumberCalculator agora está concluído e pronto para uso.

Para um cliente de exemplo que usa o componente PrimeNumberCalculator, confira Como implementar um cliente do padrão assíncrono baseado em evento.

Próximas etapas

Você pode preencher este exemplo escrevendo CalculatePrime, o equivalente síncrono do método CalculatePrimeAsync. Isso tornará o componente PrimeNumberCalculator totalmente compatível com o Padrão Assíncrono baseado em Evento.

Você pode melhorar este exemplo mantendo a lista de todos os números primos descoberto por várias invocações de diferentes números de teste. Usando essa abordagem, cada tarefa se beneficiará do trabalho realizado por tarefas anteriores. Tenha cuidado para proteger essa lista com regiões lock, para que o acesso à lista por threads diferentes seja serializado.

Você também pode melhorar este exemplo testando divisores triviais, como 2, 3 e 5.

Confira também