Partilhar via


Como: Implementar um Componente que Suporte o Padrão Assíncrono baseado em Eventos

Se estiver a escrever uma classe com algumas operações que podem incorrer em atrasos visíveis, considere atribuir-lhe funcionalidades assíncronas ao implementar a Descrição Geral do Padrão Assíncrono baseado em Eventos.

Estas instruções ilustram como criar um componente que implementa o Padrão Assíncrono baseado em Eventos. É implementado com classes auxiliares do espaço de nomes, o System.ComponentModel que garante que o componente funciona corretamente em qualquer modelo de aplicação, incluindo ASP.NET, Aplicações de consola e aplicações Windows Forms. Este componente também pode ser estruturado com um PropertyGrid controlo e os seus próprios estruturadores personalizados.

Quando terminar, terá uma aplicação que calcula os números primos de forma assíncrona. A sua aplicação terá um thread de interface de utilizador (IU) principal e um thread para cada cálculo de número primo. Embora testar se um grande número é primo pode demorar um período de tempo percetível, o thread de IU principal não será interrompido por este atraso e o formulário será reativo durante os cálculos. Poderá executar o número de cálculos que quiser em simultâneo e cancelar seletivamente cálculos pendentes.

As tarefas ilustradas nestas instruções incluem:

  • Criar o Componente

  • Definir Eventos e Delegados Assíncronos Públicos

  • Definir Delegados Privados

  • Implementar Eventos Públicos

  • Implementar o Método de Conclusão

  • Implementar os Métodos de Trabalho

  • Implementar Métodos de Início e Cancelamento

Para copiar o código neste tópico como uma única listagem, veja Como: Implementar um Cliente do Padrão Assíncrono baseado em Eventos.

Criar o Componente

O primeiro passo é criar o componente que irá implementar o Padrão Assíncrono baseado em Eventos.

Para criar o componente

  • Crie uma classe chamada PrimeNumberCalculator que herda de Component.

Definir Eventos e Delegados Assíncronos Públicos

O componente comunica com os clientes através de eventos. O evento MethodNameCompleted alerta os clientes para a 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 seu componente:

  1. Importe os System.Threading espaços de nomes e System.Collections.Specialized na parte superior do ficheiro.

    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 PrimeNumberCalculator classe, declare delegados para eventos de progresso 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 PrimeNumberCalculator classe, declare eventos para comunicar o progresso 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 PrimeNumberCalculator definição de classe, deriva a CalculatePrimeCompletedEventArgs classe para comunicar o resultado de cada cálculo ao processador de eventos do cliente para o CalculatePrimeCompletedevento .. Além das AsyncCompletedEventArgs propriedades, esta classe permite ao cliente determinar que número foi testado, se é 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 momento, pode criar o componente.

Para testar o componente

  • Compile o componente.

    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  
    

    Estes avisos serão limpos na próxima secção.

Definir Delegados Privados

Os aspetos assíncronos do PrimeNumberCalculator componente são implementados internamente com um delegado especial conhecido como SendOrPostCallback. A SendOrPostCallback representa um método de chamada de retorno que é executado num ThreadPool thread. O método de chamada de retorno tem de ter uma assinatura que utilize um único parâmetro do tipo Object, o que significa que terá de transmitir o estado entre delegados numa classe de wrapper. Para obter mais informações, consulte SendOrPostCallback.

Para implementar o comportamento assíncrono interno do componente:

  1. Declare e crie os SendOrPostCallback delegados na PrimeNumberCalculator classe . Crie os SendOrPostCallback objetos num método utilitário chamado InitializeDelegates.

    Precisará de dois delegados: um para comunicar o progresso ao cliente e outro para a conclusão de relatórios 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 InitializeDelegates método no construtor do componente.

    public PrimeNumberCalculator()
    {
        InitializeComponent();
    
        InitializeDelegates();
    }
    
    Public Sub New()
    
        InitializeComponent()
    
        InitializeDelegates()
    
    End Sub
    
  3. Declare um delegado na PrimeNumberCalculator classe que processa o trabalho real a ser feito de forma assíncrona. Este delegado encapsula o método de trabalho que testa se um número é primo. O delegado utiliza um AsyncOperation parâmetro, que será utilizado para controlar a duração 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 uma coleção para gerir durações de operações assíncronas pendentes. O cliente precisa de uma forma de controlar as operações à medida que são executadas e concluídas, e este controlo é feito ao exigir que o cliente transmita um token exclusivo, ou ID de tarefa, quando o cliente efetua a chamada para o método assíncrono. O PrimeNumberCalculator componente tem de controlar cada chamada ao associar o ID da tarefa à invocação correspondente. Se o cliente passar um ID de tarefa que não é exclusivo, o PrimeNumberCalculator componente tem de gerar uma exceção.

    O PrimeNumberCalculator componente controla o ID da tarefa ao utilizar uma classe de coleção especial denominada HybridDictionary. Na definição de classe, crie um HybridDictionary denominado 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 Eventos comunicam aos clientes através de eventos. Estes eventos são invocados no thread adequado com a ajuda da AsyncOperation classe .

Para gerar eventos para os clientes do componente:

  1. Implementar eventos públicos para relatórios para clientes. Precisará de um evento para comunicar o progresso e um para a conclusão de relatórios.

    // 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 delegado de conclusão é o método que o comportamento assíncrono subjacente e de thread livre invocará quando a operação assíncrona terminar com êxito, por preenchimento, erro ou cancelamento. Esta invocação ocorre num thread arbitrário.

Este método é onde o ID de tarefa do cliente é removido da coleção interna de tokens de cliente exclusivos. Este método também termina a duração de uma operação assíncrona específica ao chamar o PostOperationCompleted método no correspondente AsyncOperation. Esta chamada gera o evento de conclusão no thread adequado para o modelo de aplicação. Depois de o PostOperationCompleted método ser chamado, esta instância de AsyncOperation já não pode ser utilizada e quaisquer tentativas subsequentes de utilização irão gerar uma exceção.

A CompletionMethod assinatura tem de conter todo o estado necessário para descrever o resultado da operação assíncrona. Contém o estado do número que foi testado por esta operação assíncrona específica, se o número é primo e o valor do seu primeiro divisor se não for um número primo. Também contém o estado que descreve qualquer exceção que tenha ocorrido e o AsyncOperation correspondente a esta tarefa específica.

Para concluir uma operação assíncrona:

  • Implemente o método de conclusão. São necessários seis parâmetros, que utiliza para preencher um CalculatePrimeCompletedEventArgs que é devolvido ao cliente através do CalculatePrimeCompletedEventHandler. Remove o token de ID de tarefa do cliente da coleção interna e termina a duração da operação assíncrona com uma chamada para PostOperationCompleted. O AsyncOperation marshals a chamada para o thread ou contexto adequado para o modelo de aplicação.

    // 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 momento, pode criar o componente.

Para testar o componente

  • Compile o componente.

    Receberá um aviso de compilador:

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

    Este aviso será resolvido na próxima secção.

Implementar os Métodos de Trabalho

Até agora, implementou o código assíncrono de suporte para o PrimeNumberCalculator componente. Agora, pode implementar o código que faz o trabalho real. Irá implementar três métodos: CalculateWorker, BuildPrimeNumberListe IsPrime. Juntos, BuildPrimeNumberList e IsPrime compreendem um algoritmo conhecido chamado Sieve de Eratosthenes, que determina se um número é primo ao encontrar todos os números primos até à raiz quadrada do número de teste. Se não forem encontrados divisores nessa altura, o número de teste é primo.

Se este componente fosse escrito para obter a máxima eficiência, lembrar-se-ia de todos os números primos detetados por várias invocações para diferentes números de teste. Também verificaria divisores triviais como 2, 3 e 5. No entanto, a intenção deste exemplo é demonstrar como as operações demoradas podem ser executadas de forma assíncrona, para que estas otimizações sejam deixadas como um exercício para si.

O CalculateWorker método é encapsulado num delegado e é invocado de forma assíncrona com uma chamada para BeginInvoke.

Nota

Os relatórios de progresso são implementados no BuildPrimeNumberList método . Em computadores rápidos, ProgressChanged os eventos podem ser gerados em rápida sucessão. O thread de cliente, no qual estes eventos são gerados, tem de ser capaz de lidar com esta situação. O código da interface de utilizador pode ser inundado com mensagens e não conseguir acompanhar, o que resulta em falta de resposta. Para obter um exemplo de interface de utilizador que processa esta situação, veja Como: Implementar um Cliente do Padrão Assíncrono baseado em Eventos.

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

  1. Implemente o TaskCanceled método utilitário. Esta ação verifica a coleção de duração da tarefa para o ID de tarefa especificado e devolve true se o ID da tarefa não for encontrado.

    // 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. Implemente o CalculateWorker método . São precisos dois parâmetros: um número para testar 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. São necessários dois parâmetros: o número a testar e um AsyncOperation. Utiliza o para comunicar o AsyncOperation progresso e os resultados incrementais. Isto garante que os processadores de eventos do cliente são chamados no thread ou contexto adequado para o modelo de aplicação. Quando BuildPrimeNumberList encontra um número primo, comunica-o como um resultado incremental para o processador de eventos do cliente para o ProgressChanged evento. Isto requer uma classe derivada de ProgressChangedEventArgs, chamada CalculatePrimeProgressChangedEventArgs, que tem uma propriedade adicionada chamada LatestPrimeNumber.

    O BuildPrimeNumberList método também chama periodicamente o TaskCanceled método e sai se o método devolver 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. São necessários três parâmetros: uma lista de números primos conhecidos, o número a testar 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. Derivar CalculatePrimeProgressChangedEventArgs de ProgressChangedEventArgs. Esta classe é necessária para comunicar resultados incrementais ao processador de eventos do cliente para o ProgressChanged evento. Tem uma propriedade adicionada 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 momento, pode criar o componente.

Para testar o componente

  • Compile o componente.

    Tudo o que falta escrever são os métodos para iniciar e cancelar operações assíncronas e CalculatePrimeAsyncCancelAsync.

Implementar os Métodos Iniciar e Cancelar

Inicie o método de trabalho no seu próprio thread ao chamar BeginInvoke o delegado que o encapsula. Para gerir a duração de uma determinada operação assíncrona, chame o CreateOperation método na AsyncOperationManager classe auxiliar. Isto devolve um AsyncOperation, que os marshals chamam os processadores de eventos do cliente para o tópico ou contexto adequado.

Cancela uma determinada operação pendente ao chamar PostOperationCompleted o correspondente AsyncOperation. Esta ação termina essa operação e quaisquer chamadas subsequentes para o mesmo AsyncOperation irão gerar uma exceção.

Para implementar a funcionalidade Iniciar e Cancelar:

  1. Implemente o CalculatePrimeAsync método . Confirme que o token fornecido pelo cliente (ID da tarefa) é exclusivo em relação a todos os tokens que representam tarefas atualmente pendentes. Se o cliente passar num token não exclusivo, CalculatePrimeAsync gera uma exceção. Caso contrário, o token é 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. Implemente o CancelAsync método . Se o taskId parâmetro existir na coleção de tokens, será removido. Isto impede a execução de tarefas canceladas que não começaram a ser executadas. Se a tarefa estiver em execução, o BuildPrimeNumberList método sai quando deteta que o ID da tarefa foi removido da coleção de duração.

    // 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 momento, pode criar o componente.

Para testar o componente

  • Compile o componente.

O PrimeNumberCalculator componente está agora concluído e pronto a ser utilizado.

Para obter um cliente de exemplo que utiliza o PrimeNumberCalculator componente, veja Como: Implementar um Cliente do Padrão Assíncrono baseado em Eventos.

Passos Seguintes

Pode preencher este exemplo ao escrever CalculatePrime, o equivalente síncrono do CalculatePrimeAsync método . Isto fará com que o PrimeNumberCalculator componente seja totalmente compatível com o Padrão Assíncrono baseado em Eventos.

Pode melhorar este exemplo ao manter a lista de todos os números primos detetados por várias invocações para diferentes números de teste. Com esta abordagem, cada tarefa beneficiará do trabalho realizado pelas tarefas anteriores. Tenha cuidado ao proteger esta lista com lock regiões, para que o acesso à lista por threads diferentes seja serializado.

Também pode melhorar este exemplo ao testar divisores triviais, como 2, 3 e 5.

Ver também