Share via


Procedura: Implementare un componente che supporta il modello asincrono basato su eventi

In caso di scrittura di una classe con alcune operazioni che possono causare ritardi notevoli, è consigliabile assegnare la funzionalità asincrona implementando Panoramica sul modello asincrono basato su eventi.

Questa procedura dettagliata illustra come creare un componente che implementa un modello asincrono basato su eventi. L'implementazione viene eseguita usando classi helper dallo spazio dei nomi System.ComponentModel, per poter assicurare che il componente funzioni correttamente con qualsiasi modello di applicazione, tra cui ASP.NET, applicazioni console e applicazioni Windows Form. Questo componente è anche identificabile da un controllo PropertyGrid e dalle finestre di progettazione personalizzate.

Nel corso della procedura, un'applicazione calcola i numeri primi in modo asincrono. L'applicazione avrà un thread di interfaccia utente principale e un thread per ogni calcolo di numero primo. Anche se verificare che un numero a molte cifre sia un numero primo può richiedere una notevole quantità di tempo, il thread principale dell'interfaccia utente non verrà interrotto da questa operazione e la capacità di risposta del form resterà inalterata durante i calcoli. Sarà possibile eseguire simultaneamente tutti i calcoli necessari e annullare in modo selettivo i calcoli in sospeso.

Le attività illustrate nella procedura dettagliata sono le seguenti:

  • Creazione del componente

  • Definizione di delegati ed eventi asincroni pubblici

  • Definizione di delegati privati

  • Implementazione di eventi pubblici

  • Implementazione del metodo di completamento

  • Implementazione dei metodi di lavoro

  • Implementazione dei metodi di avvio e di annullamento

Per copiare il codice in questo argomento come singolo listato, vedere Procedura: Implementare un client del modello asincrono basato su eventi.

Creazione del componente

Il primo passaggio prevede la creazione del componente che implementerà il modello asincrono basato su eventi.

Per creare il componente

  • Creare una classe denominata PrimeNumberCalculator che eredita da Component.

Definizione di delegati ed eventi asincroni pubblici

Il componente comunica con i client usando gli eventi. L'evento MethodNameCompleted avvisa i client del completamento di un'attività asincrona, mentre l'evento MethodNameProgressChanged li informa dello stato di avanzamento di questa attività.

Per definire gli eventi asincroni per i client del componente:

  1. Importare gli spazi dei nomi System.Threading e System.Collections.Specialized all'inizio del file.

    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. Prima della definizione della classe PrimeNumberCalculator dichiarare i delegati per gli eventi di stato e di completamento.

    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. Nella definizione della classe PrimeNumberCalculator dichiarare gli eventi per segnalare ai client lo stato e il completamento.

    public event ProgressChangedEventHandler ProgressChanged;
    public event CalculatePrimeCompletedEventHandler CalculatePrimeCompleted;
    
    Public Event ProgressChanged _
        As ProgressChangedEventHandler
    Public Event CalculatePrimeCompleted _
        As CalculatePrimeCompletedEventHandler
    
  4. Dopo la definizione della classe PrimeNumberCalculator, derivare la classe CalculatePrimeCompletedEventArgs per segnalare l'esito di ogni calcolo al gestore eventi del client per l'evento CalculatePrimeCompleted. Oltre alle proprietà AsyncCompletedEventArgs, questa classe consente al client di determinare quale numero è stato testato, se è un numero primo e qual è il primo divisore se non è un numero 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
    

Checkpoint 1

A questo punto, è possibile compilare il componente.

Per eseguire il test del componente

  • Compilare il componente.

    Verranno visualizzati due avvisi del compilatore:

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

    Questi avvisi verranno cancellati nella sezione successiva.

Definizione di delegati privati

Gli aspetti asincroni del componente PrimeNumberCalculator vengono implementati internamente con uno speciale delegato noto come SendOrPostCallback. Un delegato SendOrPostCallback rappresenta un metodo di callback eseguito su un thread ThreadPool. Il metodo di callback deve avere una firma che accetta un singolo parametro di tipo Object e sarà quindi necessario passare lo stato tra i delegati in una classe wrapper. Per altre informazioni, vedere SendOrPostCallback.

Per implementare il comportamento asincrono interno del componente:

  1. Dichiarare e creare i delegati SendOrPostCallback nella classe PrimeNumberCalculator. Creare gli oggetti SendOrPostCallback in un metodo di utilità denominato InitializeDelegates.

    Saranno necessari due delegati: uno per segnalare lo stato al client e uno per segnalare il completamento al client.

    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. Chiamare il metodo InitializeDelegates nel costruttore del componente.

    public PrimeNumberCalculator()
    {
        InitializeComponent();
    
        InitializeDelegates();
    }
    
    Public Sub New()
    
        InitializeComponent()
    
        InitializeDelegates()
    
    End Sub
    
  3. Dichiarare un delegato nella classe PrimeNumberCalculator che gestisce il lavoro effettivo da eseguire in modo asincrono. Questo delegato esegue il wrapping del metodo di lavoro che verifica se un numero è primo. Il delegato accetta un parametro AsyncOperation, che verrà usato per tenere traccia della durata dell'operazione asincrona.

    private delegate void WorkerEventHandler(
        int numberToCheck,
        AsyncOperation asyncOp);
    
    Private Delegate Sub WorkerEventHandler( _
    ByVal numberToCheck As Integer, _
    ByVal asyncOp As AsyncOperation)
    
  4. Creare una raccolta per la gestione delle durate delle operazioni asincrone in sospeso. Il client deve poter tenere traccia delle operazioni mentre vengono eseguite e completate e, a questo scopo, si richiede al client di passare un token univoco, ovvero un ID attività, quando il client effettua la chiamata al metodo asincrono. Il componente PrimeNumberCalculator deve tenere traccia di ogni chiamata associando l'ID attività alla chiamata corrispondente. Se il client passa un ID attività non univoco, il componente PrimeNumberCalculator deve generare un'eccezione.

    Il componente PrimeNumberCalculator tiene traccia dell'ID attività usando una speciale classe di raccolta denominata HybridDictionary. Nella definizione della classe creare una classe HybridDictionary denominata userStateToLifetime.

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

Implementazione di eventi pubblici

I componenti che implementano il modello asincrono basato su eventi comunicano con i client usando gli eventi. Questi eventi vengono richiamati sul thread appropriato grazie alla classe AsyncOperation.

Per generare eventi per i client del componente:

  1. Implementare gli eventi pubblici per la segnalazione ai client. Saranno necessari un evento per segnalare lo stato e uno per segnalare il completamento.

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

Implementazione del metodo di completamento

Il delegato di completamento è il metodo che verrà richiamato dal comportamento asincrono a thread libero sottostante quando l'operazione asincrona terminerà con completamento corretto, errore o annullamento. Questa chiamata si verifica su un thread arbitrario.

È in questo metodo che l'ID attività del client viene rimosso dalla raccolta interna di token client univoci. Questo metodo termina anche la durata di una determinata operazione asincrona chiamando il metodo PostOperationCompleted sulla classe AsyncOperation corrispondente. Questa chiamata genera l'evento di completamento nel thread appropriato per il modello di applicazione. Dopo la chiamata al metodo PostOperationCompleted, questa istanza di AsyncOperation non può più essere usata ed eventuali tentativi successivi di usarla genereranno un'eccezione.

La firma CompletionMethod deve contenere tutti gli stati necessari per descrivere l'esito dell'operazione asincrona. Contiene lo stato per il numero testato da questa particolare operazione asincrona, lo stato che indica se il numero è primo e il valore del primo divisore se non è un numero primo. Contiene anche lo stato che descrive eventuali eccezioni generate e la classe AsyncOperation corrispondente a questa particolare attività.

Per completare un'operazione asincrona:

  • Implementare il metodo di completamento. Accetta sei parametri, che usa per popolare CalculatePrimeCompletedEventArgs restituito al client tramite CalculatePrimeCompletedEventHandler del client. Rimuove il token ID attività del client dalla raccolta interna e termina la durata dell'operazione asincrona con una chiamata a PostOperationCompleted. AsyncOperation effettua il marshalling della chiamata al thread o al contesto adeguato al modello di applicazione.

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

Checkpoint 2

A questo punto, è possibile compilare il componente.

Per eseguire il test del componente

  • Compilare il componente.

    Verrà visualizzato un avviso del compilatore:

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

    Questo avviso verrà risolto nella sezione successiva.

Implementazione dei metodi di lavoro

Fino a questo punto, è stato implementato il codice asincrono di supporto per il componente PrimeNumberCalculator. È ora possibile implementare il codice che esegue il lavoro effettivo. Si implementeranno tre metodi: CalculateWorker, BuildPrimeNumberList e IsPrime. BuildPrimeNumberList e IsPrime costituiscono un noto algoritmo chiamato Crivello di Eratostene, che determina se un numero è primo trovando tutti i numeri primi fino alla radice quadrata del numero di test. Se entro quel punto non vengono trovati divisori, il numero di test è un numero primo.

Se questo componente fosse davvero efficiente, memorizzerebbe tutti i numeri primi individuati dalle varie chiamate per i diversi numeri di test. Cercherebbe anche i divisori irrilevanti, ad esempio 2, 3 e 5. Lo scopo di questo esempio è tuttavia dimostrare come le operazioni che richiedono molto tempo possono essere eseguite in modo asincrono, quindi queste ottimizzazioni possono diventare il tema di un esercizio per l'utente.

Il metodo CalculateWorker viene sottoposto a wrapping in un delegato e richiamato in modo asincrono con una chiamata a BeginInvoke.

Nota

Il report dello stato viene implementato nel metodo BuildPrimeNumberList. Nei computer veloci gli eventi ProgressChanged possono essere generati in rapida successione. Il thread del client, in cui questi eventi vengono generati, deve poter gestire questa situazione. Il codice dell'interfaccia utente potrebbe non riuscire a stare al passo perché sommerso di messaggi e di conseguenza bloccarsi. Per un'interfaccia utente di esempio che gestisce questa situazione, vedere Procedura: Implementare un client del modello asincrono basato su eventi.

Per eseguire in modo asincrono il calcolo dei numeri primi:

  1. Implementare il metodo di utilità TaskCanceled, che controlla la raccolta della durata dell'attività per l'ID attività specificato e restituisce true se l'ID attività non viene trovato.

    // 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. Implementare il metodo CalculateWorker. Accetta due parametri: un numero da testare e una classe 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. Implementare BuildPrimeNumberList. Accetta due parametri: il numero da testare e una classe AsyncOperation. Usa la classe AsyncOperation per segnalare lo stato e i risultati incrementali. Questo assicura che i gestori evento del client vengano chiamati sul thread o contesto corretto per il modello dell'applicazione. Quando BuildPrimeNumberList trova un numero primo, lo segnala come risultato incrementale al gestore eventi del client per l'evento ProgressChanged. A questo scopo, è necessaria una classe derivata da ProgressChangedEventArgs, chiamata CalculatePrimeProgressChangedEventArgs, con una proprietà aggiunta chiamata LatestPrimeNumber.

    Il metodo BuildPrimeNumberList chiama anche periodicamente il metodo TaskCanceled ed esce se il metodo restituisce 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. Implementare IsPrime. Il metodo accetta tre parametri: un elenco di numeri primi noti, il numero da testare e un parametro di output per il primo divisore trovato. Dato l'elenco di numeri primi, determina se il numero di test è un numero 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. Derivare CalculatePrimeProgressChangedEventArgs da ProgressChangedEventArgs. Questa classe è necessaria per segnalare i risultati incrementali al gestore eventi del client per l'evento ProgressChanged. Contiene una proprietà aggiuntiva denominata 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
    

Checkpoint 3

A questo punto, è possibile compilare il componente.

Per eseguire il test del componente

  • Compilare il componente.

    Gli unici elementi ancora da scrivere sono i metodi che avviano e annullano le operazioni asincrone, CalculatePrimeAsync e CancelAsync.

Implementazione dei metodi di annullamento e di avvio

Per avviare il metodo di lavoro nel relativo thread, chiamare BeginInvoke sul delegato che ne esegue il wrapping. Per gestire la durata di una determinata operazione asincrona, si chiama il metodo CreateOperation sulla classe helper AsyncOperationManager. Viene restituita una classe AsyncOperation, che effettua il marshalling delle chiamate sui gestori eventi del client al thread o al contesto appropriato.

Per annullare una determinata operazione in sospeso, chiamare PostOperationCompleted sulla classe AsyncOperation corrispondente. Verrà terminata l'operazione e tutte le chiamate successive alla classe AsyncOperation genereranno un'eccezione.

Per implementare la funzionalità di avvio e annullamento:

  1. Implementare il metodo CalculatePrimeAsync. Verificare che il token fornito dal client (ID attività) sia univoco rispetto a tutti i token che rappresentano le attività attualmente in sospeso. Se il client passa un token non univoco, CalculatePrimeAsync genera un'eccezione. In caso contrario, il token viene aggiunto alla raccolta di ID attività.

    // 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. Implementare il metodo CancelAsync. Se il parametro taskId esiste nella raccolta di token, viene rimosso. In questo modo viene impedita l'esecuzione delle attività annullate non avviate. Se l'attività è in esecuzione, il metodo BuildPrimeNumberList termina quando rileva che l'ID attività è stato rimosso dalla raccolta della durata.

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

Checkpoint 4

A questo punto, è possibile compilare il componente.

Per eseguire il test del componente

  • Compilare il componente.

Il componente PrimeNumberCalculator è ora completo e pronto per l'uso.

Per un client di esempio che usa il componente PrimeNumberCalculator, vedere Procedura: Implementare un client del modello asincrono basato su eventi.

Passaggi successivi

È possibile compilare questo esempio scrivendo CalculatePrime, l'equivalente sincrono del metodo CalculatePrimeAsync. In questo modo il componente PrimeNumberCalculator risulta completamente compatibile con il modello asincrono basato su eventi.

È possibile migliorare questo esempio conservando l'elenco di tutti i numeri primi individuati dalle varie chiamate per i diversi numeri di test. Usando questo approccio, ogni attività usufruirà del lavoro svolto dalle attività precedenti. Assicurarsi di proteggere l'elenco con aree lock, in modo che l'accesso all'elenco da thread diversi venga serializzato.

È anche possibile migliorare l'esempio testando i divisori semplici, ad esempio 2, 3 e 5.

Vedi anche