Procedura dettagliata: implementazione di un componente che supporta il modello asincrono basato su eventi
Aggiornamento: novembre 2007
Se la scrittura di una classe implica operazioni che possono causare ritardi notevoli, è possibile assegnare alla classe la funzionalità asincrona implementando Cenni preliminari sul modello asincrono basato su eventi.
In questa procedura dettagliata viene illustrato come creare un componente che implementa il modello asincrono basato su eventi. L'implementazione viene eseguita mediante le classi di supporto dello spazio dei nomi System.ComponentModel. In questo modo è garantito il funzionamento del componente in qualsiasi modello di applicazione, incluse le applicazioni ASP.NET, console e Windows Form. Questo componente può anche essere progettato con un controllo PropertyGrid e con strumenti di progettazione personalizzati.
Nel corso della procedura si disporrà di un'applicazione che consente di calcolare in modo asincrono i numeri primi. L'applicazione in questione contiene un thread dell'interfaccia utente principale e un thread per ogni calcolo eseguito sui numeri primi. Sebbene il test eseguito per verificare se un numero grande è un numero primo possa richiedere una notevole quantità di tempo, il thread dell'interfaccia utente principale non verrà interrotto da questo ritardo e il form sarà reattivo nel corso dei calcoli. Sarà possibile eseguire il numero desiderato di calcoli concorrenti e annullare in modo selettivo i calcoli in sospeso.
Di seguito sono elencate le attività illustrate nella procedura dettagliata:
Creazione del componente
Definizione di eventi e delegati 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 annullamento
Per copiare il codice nell'argomento corrente come un elenco singolo, vedere Procedura: implementare un componente che supporta il modello asincrono basato su eventi.
Creazione del componente
Il primo passaggio consiste nella creazione del componente che implementerà il modello asincrono basato su eventi.
Per creare un componente
- Creare una classe denominata PrimeNumberCalculator che eredita da Component.
Definizione di eventi e delegati asincroni pubblici
Il componente comunica con i client per mezzo degli eventi. L'evento NomeMetodoCompleted avvisa i client del completamento di un'attività asincrona, mentre l'evento NomeMetodoProgressChanged li informa dello stato di avanzamento di tale attività.
Per definire eventi asincroni per i client del componente:
Importare gli spazi dei nomi System.Threading e System.Collections.Specialized nella parte superiore del file.
Imports System Imports System.Collections Imports System.Collections.Specialized Imports System.ComponentModel Imports System.Drawing Imports System.Globalization Imports System.Threading Imports System.Windows.Forms
using System; using System.Collections; using System.Collections.Specialized; using System.ComponentModel; using System.Data; using System.Drawing; using System.Globalization; using System.Threading; using System.Windows.Forms;
import System.*; import System.Collections.*; import System.Collections.Specialized.*; import System.ComponentModel.*; import System.Data.*; import System.Drawing.*; import System.Threading.*; import System.Windows.Forms.*;
Prima della definizione della classe PrimeNumberCalculator , dichiarare i delegati per gli eventi di avanzamento e completamento.
Public Delegate Sub ProgressChangedEventHandler( _ ByVal e As ProgressChangedEventArgs) Public Delegate Sub CalculatePrimeCompletedEventHandler( _ ByVal sender As Object, _ ByVal e As CalculatePrimeCompletedEventArgs)
public delegate void ProgressChangedEventHandler( ProgressChangedEventArgs e); public delegate void CalculatePrimeCompletedEventHandler( object sender, CalculatePrimeCompletedEventArgs e);
public delegate void ProgressChangedEventHandler(ProgressChangedEventArgs e); /** @delegate */ public delegate void CalculatePrimeCompletedEventHandler(Object sender, CalculatePrimeCompletedEventArgs e);
Prima della definizione della classe PrimeNumberCalculator , dichiarare gli eventi per la generazione di report sullo stato di avanzamento e sul completamento destinati ai client.
Public Event ProgressChanged _ As ProgressChangedEventHandler Public Event CalculatePrimeCompleted _ As CalculatePrimeCompletedEventHandler
public event ProgressChangedEventHandler ProgressChanged; public event CalculatePrimeCompletedEventHandler CalculatePrimeCompleted;
public ProgressChangedEventHandler progressChanged = null; /** @event */ public void add_ProgressChanged(ProgressChangedEventHandler p) { progressChanged =(ProgressChangedEventHandler)System.Delegate. Combine(progressChanged, p); } /** @event */ public void remove_ProgressChanged(ProgressChangedEventHandler p) { progressChanged =(ProgressChangedEventHandler)System.Delegate. Remove(progressChanged, p); } public CalculatePrimeCompletedEventHandler calculatePrimeCompleted = null; /** @event */ public void add_CalculatePrimeCompleted(CalculatePrimeCompletedEventHandler p) { calculatePrimeCompleted =(CalculatePrimeCompletedEventHandler) System.Delegate.Combine(calculatePrimeCompleted, p); } /** @event */ public void remove_CalculatePrimeCompleted( CalculatePrimeCompletedEventHandler p) { calculatePrimeCompleted =(CalculatePrimeCompletedEventHandler) System.Delegate.Remove(calculatePrimeCompleted, p); }
Dopo la definizione della classe PrimeNumberCalculator , derivare la classe CalculatePrimeCompletedEventArgs per la generazione del report sul risultato di ogni calcolo destinato al gestore eventi del client relativo all'evento CalculatePrimeCompleted. Oltre alle proprietà AsyncCompletedEventArgs, questa classe consente al client di determinare il numero sottoposto a test, se si tratta di un numero primo e il valore del primo divisore, se non è numero primo.
Public Class CalculatePrimeCompletedEventArgs Inherits AsyncCompletedEventArgs Private numberToTestValue As Integer = 0 Private firstDivisorValue As Integer = 1 Private isPrimeValue As Boolean Public Sub New( _ ByVal numberToTest As Integer, _ ByVal firstDivisor As Integer, _ ByVal isPrime As Boolean, _ ByVal e As Exception, _ ByVal canceled As Boolean, _ ByVal state As Object) MyBase.New(e, canceled, state) Me.numberToTestValue = numberToTest Me.firstDivisorValue = firstDivisor Me.isPrimeValue = isPrime End Sub Public ReadOnly Property NumberToTest() As Integer Get ' Raise an exception if the operation failed ' or was canceled. RaiseExceptionIfNecessary() ' If the operation was successful, return ' the property value. Return numberToTestValue End Get End Property Public ReadOnly Property FirstDivisor() As Integer Get ' Raise an exception if the operation failed ' or was canceled. RaiseExceptionIfNecessary() ' If the operation was successful, return ' the property value. Return firstDivisorValue End Get End Property Public ReadOnly Property IsPrime() As Boolean Get ' Raise an exception if the operation failed ' or was canceled. RaiseExceptionIfNecessary() ' If the operation was successful, return ' the property value. Return isPrimeValue End Get End Property End Class
public class CalculatePrimeCompletedEventArgs : AsyncCompletedEventArgs { private int numberToTestValue = 0; private int firstDivisorValue = 1; private bool isPrimeValue; public CalculatePrimeCompletedEventArgs( int numberToTest, int firstDivisor, bool isPrime, Exception e, bool canceled, object state) : base(e, canceled, state) { this.numberToTestValue = numberToTest; this.firstDivisorValue = firstDivisor; this.isPrimeValue = isPrime; } public int NumberToTest { get { // Raise an exception if the operation failed or // was canceled. RaiseExceptionIfNecessary(); // If the operation was successful, return the // property value. return numberToTestValue; } } public int FirstDivisor { get { // Raise an exception if the operation failed or // was canceled. RaiseExceptionIfNecessary(); // If the operation was successful, return the // property value. return firstDivisorValue; } } public bool IsPrime { get { // Raise an exception if the operation failed or // was canceled. RaiseExceptionIfNecessary(); // If the operation was successful, return the // property value. return isPrimeValue; } } }
public class CalculatePrimeCompletedEventArgs extends AsyncCompletedEventArgs { private int numberToTestValue = 0; private int firstDivisorValue = 1; private boolean isPrimeValue; public CalculatePrimeCompletedEventArgs(int numberToTest, int firstDivisor, boolean isPrime, System.Exception e, boolean cancelled, Object state) { super(e, cancelled, state); this.numberToTestValue = numberToTest; this.firstDivisorValue = firstDivisor; this.isPrimeValue = isPrime; } /** @property */ public int get_NumberToTest() { // Raise an exception if the operation failed or // was cancelled. RaiseExceptionIfNecessary(); // If the operation was successful, return the // property value. return numberToTestValue; } /** @property */ public int get_FirstDivisor() { // Raise an exception if the operation failed or // was cancelled. RaiseExceptionIfNecessary(); // If the operation was successful, return the // property value. return firstDivisorValue; } /** @property */ public boolean get_IsPrime() { // Raise an exception if the operation failed or // was cancelled. RaiseExceptionIfNecessary(); // If the operation was successful, return the // property value. return isPrimeValue; } }
Verifica
A questo punto, è possibile generare il componente.
Per eseguire il test del componente
Compilare il componente.
Verranno visualizzati due messaggi di avviso del compilatore:
warning CS0067: The event 'AsynchronousPatternExample.PrimeNumberCalculator.ProgressChanged' is never used warning CS0067: The event 'AsynchronousPatternExample.PrimeNumberCalculator.CalculatePrimeCompleted' is never used
Questi messaggi verranno eliminati nella sezione successiva.
Definizione di delegati privati
Le caratteristiche asincrone del componente PrimeNumberCalculator vengono implementate internamente con un delegato speciale noto come SendOrPostCallback che rappresenta un metodo di callback eseguito su un thread ThreadPool. Il metodo di callback deve presentare una firma che accetti un solo parametro di tipo Object. In altre parole, sarà necessario passare lo stato tra i delegati di una classe wrapper. Per ulteriori informazioni, vedere SendOrPostCallback.
Per implementare il comportamento asincrono interno del componente:
Dichiarare e creare i delegati SendOrPostCallback nella classe PrimeNumberCalculator . Creare gli oggetti SendOrPostCallback in un metodo di utilità denominato InitializeDelegates.
Saranno necessari due delegati, ovvero uno per segnalare lo stato di avanzamento al client e uno per informarlo del completamento dell'operazione.
Private onProgressReportDelegate As SendOrPostCallback Private onCompletedDelegate As SendOrPostCallback ... Protected Overridable Sub InitializeDelegates() onProgressReportDelegate = _ New SendOrPostCallback(AddressOf ReportProgress) onCompletedDelegate = _ New SendOrPostCallback(AddressOf CalculateCompleted) End Sub
private SendOrPostCallback onProgressReportDelegate; private SendOrPostCallback onCompletedDelegate; ... protected virtual void InitializeDelegates() { onProgressReportDelegate = new SendOrPostCallback(ReportProgress); onCompletedDelegate = new SendOrPostCallback(CalculateCompleted); }
private SendOrPostCallback onProgressReportDelegate; private SendOrPostCallback onCompletedDelegate; ... protected void InitializeDelegates() { onProgressReportDelegate = new SendOrPostCallback(ReportProgress); onCompletedDelegate = new SendOrPostCallback(CalculateCompleted); }
Chiamare il metodo InitializeDelegates nel costruttore del componente.
Public Sub New() InitializeComponent() InitializeDelegates() End Sub
public PrimeNumberCalculator() { InitializeComponent(); InitializeDelegates(); }
public PrimeNumberCalculator() { /// /// Required for Windows.Forms Class Composition Designer support /// InitializeComponent(); InitializeDelegates(); }
Dichiarare un delegato nella classePrimeNumberCalculatorche gestisce il lavoro effettivo da eseguire in modo asincrono. Questo delegato esegue il wrapping del metodo di lavoro che esegue il test per verificare se un numero è numero primo. Il delegato accetta un parametro AsyncOperation che verrà utilizzato per tenere traccia del ciclo di vita dell'operazione asincrona.
Private Delegate Sub WorkerEventHandler( _ ByVal numberToCheck As Integer, _ ByVal asyncOp As AsyncOperation)
private delegate void WorkerEventHandler( int numberToCheck, AsyncOperation asyncOp);
private delegate void WorkerEventHandler( int numberToCheck, AsyncOperation asyncOp );
Creare un insieme per la gestione dei cicli di vita delle operazioni asincrone in sospeso. Al client è necessario un sistema per tenere traccia delle operazioni nel corso dell'esecuzione e al completamento e tale sistema consiste nella richiesta di passare un token univoco o un ID attività quando il client effettua la chiamata al metodo asincrono. Il componentePrimeNumberCalculatordeve tenere traccia di ogni chiamata associando l'ID attività al relativo richiamo. Se il client passa un ID attività non univoco, il componente PrimeNumberCalculator deve generare un'eccezione.
Il componentePrimeNumberCalculatortiene traccia dell'ID attività utilizzando una classe di insieme speciale denominata HybridDictionary. Nella definizione della classe creare un oggetto HybridDictionary denominato userTokenToLifetime.
Private userStateToLifetime As New HybridDictionary()
private HybridDictionary userStateToLifetime = new HybridDictionary();
private HybridDictionary userStateToLifetime = new HybridDictionary();
Implementazione di eventi pubblici
I componenti che implementano il modello asincrono basato su eventi comunicano con i client mediante gli eventi. Tali eventi vengono richiamati sul thread appropriato con il supporto della classe AsyncOperation.
Per generare eventi per i client del componente:
Implementare gli eventi pubblici per la generazione di report destinati ai client. Sono necessari un evento per la generazione di report sullo stato di avanzamento e uno per la generazione di report sul completamento.
' This method is invoked via the AsyncOperation object, ' so it is guaranteed to be executed on the correct thread. Private Sub CalculateCompleted(ByVal operationState As Object) Dim e As CalculatePrimeCompletedEventArgs = operationState OnCalculatePrimeCompleted(e) End Sub ' This method is invoked via the AsyncOperation object, ' so it is guaranteed to be executed on the correct thread. Private Sub ReportProgress(ByVal state As Object) Dim e As ProgressChangedEventArgs = state OnProgressChanged(e) End Sub Protected Sub OnCalculatePrimeCompleted( _ ByVal e As CalculatePrimeCompletedEventArgs) RaiseEvent CalculatePrimeCompleted(Me, e) End Sub Protected Sub OnProgressChanged( _ ByVal e As ProgressChangedEventArgs) RaiseEvent ProgressChanged(e) End Sub
// This method is invoked via the AsyncOperation object, // so it is guaranteed to be executed on the correct thread. private void CalculateCompleted(object operationState) { CalculatePrimeCompletedEventArgs e = operationState as CalculatePrimeCompletedEventArgs; OnCalculatePrimeCompleted(e); } // This method is invoked via the AsyncOperation object, // so it is guaranteed to be executed on the correct thread. private void ReportProgress(object state) { ProgressChangedEventArgs e = state as ProgressChangedEventArgs; OnProgressChanged(e); } protected void OnCalculatePrimeCompleted( CalculatePrimeCompletedEventArgs e) { if (CalculatePrimeCompleted != null) { CalculatePrimeCompleted(this, e); } } protected void OnProgressChanged(ProgressChangedEventArgs e) { if (ProgressChanged != null) { ProgressChanged(e); } }
// You are guaranteed to be on the correct thread, as // this method is invoked via the AsyncOperation object. private void CalculateCompleted(Object operationState) { CalculatePrimeCompletedEventArgs e = (CalculatePrimeCompletedEventArgs)operationState; OnCalculatePrimeCompleted(e); } // You are guaranteed to be on the correct thread, as // this method is invoked via the AsyncOperation object. private void ReportProgress(Object state) { ProgressChangedEventArgs e = (ProgressChangedEventArgs)state; OnProgressChanged(e); } protected void OnCalculatePrimeCompleted(CalculatePrimeCompletedEventArgs e) { if (calculatePrimeCompleted != null) { calculatePrimeCompleted.Invoke(this, e); } } protected void OnProgressChanged(ProgressChangedEventArgs e) { if (progressChanged != null) { progressChanged.Invoke(e); } }
Implementazione del metodo di completamento
Il delegato di completamento è il metodo richiamato dal comportamento asincrono di threading Free sottostante quando un'operazione asincrona viene terminata per completamento, errore o annullamento. Questo richiamo si verifica su un thread arbitrario.
Il metodo in questione rappresenta la posizione in cui l'ID attività del client viene rimosso dall'insieme interno di token client univoci e termina il ciclo di vita di una determinata operazione asincrona mediante la chiamata del metodo PostOperationCompleted sull'oggetto AsyncOperation corrispondente. Questa chiamata genera l'evento di completamento sul thread appropriato per il modello di applicazione. In seguito alla chiamata del metodo PostOperationCompleted, questa istanza di AsyncOperation non potrà essere più utilizzata. Pertanto, tutti i tentativi successivi di utilizzo genereranno un'eccezione.
La firma CompletionMethod deve contenere qualsiasi stato necessario a descrivere il risultato dell'operazione asincrona. Il parametro contiene lo stato relativo al numero sottoposto a test nel corso dell'operazione asincrona, la specifica di numero primo, se applicabile, e il valore del primo divisore, se non corrisponde a un numero primo, nonché lo stato che descrive le eventuali eccezioni generate e l'oggetto AsyncOperation corrispondente all'operazione in questione.
Per completare un'operazione asincrona:
Implementare il metodo di completamento Accetta sei parametri che utilizza per inserire i dati in un oggetto CalculatePrimeCompletedEventArgs restituito al client tramite l'oggetto CalculatePrimeCompletedEventHandler del client. Tale metodo rimuove l'ID attività del client dall'insieme interno e termina il ciclo di vita dell'operazione asincrona con una chiamata al metodo PostOperationCompleted. L'oggetto AsyncOperation esegue il marshalling della chiamata al thread o al contesto appropriato per il modello di applicazione.
' This is the method that the underlying, free-threaded ' asynchronous behavior will invoke. This will happen on ' an arbitrary thread. Private Sub CompletionMethod( _ ByVal numberToTest As Integer, _ ByVal firstDivisor As Integer, _ ByVal prime As Boolean, _ ByVal exc As Exception, _ ByVal canceled As Boolean, _ ByVal asyncOp As AsyncOperation) ' If the task was not previously canceled, ' remove the task from the lifetime collection. If Not canceled Then SyncLock userStateToLifetime.SyncRoot userStateToLifetime.Remove(asyncOp.UserSuppliedState) End SyncLock End If ' Package the results of the operation in a ' CalculatePrimeCompletedEventArgs. Dim e As New CalculatePrimeCompletedEventArgs( _ numberToTest, _ firstDivisor, _ prime, _ exc, _ canceled, _ asyncOp.UserSuppliedState) ' End the task. The asyncOp object is responsible ' for marshaling the call. asyncOp.PostOperationCompleted(onCompletedDelegate, e) ' Note that after the call to PostOperationCompleted, asyncOp ' is no longer usable, and any attempt to use it will cause. ' an exception to be thrown. End Sub
// This is the method that the underlying, free-threaded // asynchronous behavior will invoke. This will happen on // an arbitrary thread. private void CompletionMethod( int numberToTest, int firstDivisor, bool isPrime, Exception exception, bool canceled, AsyncOperation asyncOp ) { // If the task was not previously canceled, // remove the task from the lifetime collection. if (!canceled) { lock (userStateToLifetime.SyncRoot) { userStateToLifetime.Remove(asyncOp.UserSuppliedState); } } // Package the results of the operation in a // CalculatePrimeCompletedEventArgs. CalculatePrimeCompletedEventArgs e = new CalculatePrimeCompletedEventArgs( numberToTest, firstDivisor, isPrime, exception, canceled, asyncOp.UserSuppliedState); // End the task. The asyncOp object is responsible // for marshaling the call. asyncOp.PostOperationCompleted(onCompletedDelegate, e); // Note that after the call to OperationCompleted, // asyncOp is no longer usable, and any attempt to use it // will cause an exception to be thrown. }
// 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, boolean isPrime, System.Exception exception, boolean canceled, AsyncOperation asyncOp) { // If the task was not previously canceled, // remove the task from the lifetime collection. if (!canceled) { synchronized (userStateToLifetime.get_SyncRoot()) { userStateToLifetime.Remove(asyncOp.get_UserSuppliedState()); } } // Package the results of the operation in a // CalculatePrimeCompletedEventArgs. CalculatePrimeCompletedEventArgs e = new CalculatePrimeCompletedEventArgs( numberToTest, firstDivisor, isPrime, exception, canceled, asyncOp.get_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. } // 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.
Verifica
A questo punto, è possibile generare il componente.
Per eseguire il test del componente
Compilare il componente.
Verrà visualizzato un messaggio di avviso del compilatore:
warning CS0169: The private field 'AsynchronousPatternExample.PrimeNumberCalculator.workerDelegate' is never used
Questo messaggio verrà eliminato nella sezione successiva.
Implementazione dei metodi di lavoro
Finora è stato implementato il codice asincrono di supporto del componentePrimeNumberCalculator. A questo punto, è possibile implementare il codice che esegue le operazioni effettive. Verranno implementati tre metodi:CalculateWorker, BuildPrimeNumberList e IsPrime. Insieme, i metodiBuildPrimeNumberListeIsPrimeformano un algoritmo, noto come crivello di Eratostene, che determina se un numero è primo mediante la ricerca di tutti i numeri primi fino alla radice quadrata del numero sottoposto a test. Se non vengono individuati divisori in corrispondenza di tale punto, il numero sottoposto a test è un numero primo.
Se il componente fosse stato scritto per ottenere i massimi risultati, avrebbe tenuto traccia di tutti i numeri primi rilevati dai vari richiami dei diversi numeri sottoposti a test e avrebbe verificato la presenza di divisori triviali come 2, 3 e 5. Lo scopo di questo esempio è dimostrare come eseguire in modo asincrono operazioni che richiedono molto tempo. Pertanto, questi esempi di ottimizzazione vengono lasciati come esercizio per l'utente.
Il metodo CalculateWorker viene sottoposto a wrapping in un delegato e richiamato in modo asincrono con una chiamata a BeginInvoke.
![]() |
---|
La segnalazione dello stato di avanzamento è implementata nel metodo BuildPrimeNumberList. Nei computer veloci gli eventi ProgressChanged possono essere generati in rapida successione. Il thread del client, su cui vengono generati tali eventi, deve essere in grado di gestire la situazione. Il codice dell'interfaccia utente può venire sovraccaricato dai messaggi e non riuscire a tenere il passo, giungendo a un punto di stallo. Per un esempio di interfaccia utente in grado di gestire tale situazione, vedere Procedura: implementare un client del modello asincrono basato su eventi. |
Per eseguire in modo asincrono il calcolo dei numeri primi:
Implementare il metodo di utilità TaskCanceled. Viene così verificato se nell'insieme dei cicli di vita delle attività è presente l'ID attività specificato e, in caso negativo, viene restituito true.
' Utility method for determining if a ' task has been canceled. Private Function TaskCanceled(ByVal taskId As Object) As Boolean Return (userStateToLifetime(taskId) Is Nothing) End Function
// Utility method for determining if a // task has been canceled. private bool TaskCanceled(object taskId) { return( userStateToLifetime[taskId] == null ); }
// Utility method for determining if a // task has been canceled. private boolean TaskCanceled(Object taskId) { return (!userStateToLifetime.Contains(taskId) ); }
Implementare il metodo CalculateWorker. Il metodo accetta due parametri, ovvero il numero da sottoporre a test e un oggetto AsyncOperation,
' This method performs the actual prime number computation. ' It is executed on the worker thread. Private Sub CalculateWorker( _ ByVal numberToTest As Integer, _ ByVal asyncOp As AsyncOperation) Dim prime As Boolean = False Dim firstDivisor As Integer = 1 Dim exc As Exception = Nothing ' Check that the task is still active. ' The operation may have been canceled before ' the thread was scheduled. If Not Me.TaskCanceled(asyncOp.UserSuppliedState) Then Try ' Find all the prime numbers up to the ' square root of numberToTest. Dim primes As ArrayList = BuildPrimeNumberList( _ numberToTest, asyncOp) ' Now we have a list of primes less than 'numberToTest. prime = IsPrime( _ primes, _ numberToTest, _ firstDivisor) Catch ex As Exception exc = ex End Try End If Me.CompletionMethod( _ numberToTest, _ firstDivisor, _ prime, _ exc, _ TaskCanceled(asyncOp.UserSuppliedState), _ asyncOp) End Sub
// This method performs the actual prime number computation. // It is executed on the worker thread. private void CalculateWorker( int numberToTest, AsyncOperation asyncOp) { bool isPrime = false; int firstDivisor = 1; Exception e = null; // Check that the task is still active. // The operation may have been canceled before // the thread was scheduled. if (!TaskCanceled(asyncOp.UserSuppliedState)) { try { // Find all the prime numbers up to // the square root of numberToTest. ArrayList primes = BuildPrimeNumberList( numberToTest, asyncOp); // Now we have a list of primes less than // numberToTest. isPrime = IsPrime( primes, numberToTest, out firstDivisor); } catch (Exception ex) { e = ex; } } //CalculatePrimeState calcState = new CalculatePrimeState( // numberToTest, // firstDivisor, // isPrime, // e, // TaskCanceled(asyncOp.UserSuppliedState), // asyncOp); //this.CompletionMethod(calcState); this.CompletionMethod( numberToTest, firstDivisor, isPrime, e, TaskCanceled(asyncOp.UserSuppliedState), asyncOp); //completionMethodDelegate(calcState); }
private void CalculateWorker( int numberToTest, AsyncOperation asyncOp) { boolean isPrime = false; int firstDivisor = 1; System.Exception exception = null; // Check that the task is still active. // The operation may have been canceled before // the thread was scheduled. if (!TaskCanceled(asyncOp.get_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 (System.Exception ex) { exception = ex; } } this.CompletionMethod( numberToTest, firstDivisor, isPrime, exception, TaskCanceled(asyncOp.get_UserSuppliedState()), asyncOp); }
Implementare BuildPrimeNumberList Il metodo accetta due parametri, ovvero il numero da sottoporre a test e un oggetto AsyncOperation, e utilizza l'oggetto AsyncOperation per generare report sullo stato di avanzamento e sui risultati incrementali. In questo modo è garantito che i gestori eventi del client vengano chiamati sul thread o sul contesto appropriato al modello di applicazione. Quando BuildPrimeNumberList trova un numero primo, lo inserisce come risultato incrementale in un report destinato al gestore eventi del client per l'evento ProgressChanged. È richiesta una classe derivata da ProgressChangedEventArgs, denominata CalculatePrimeProgressChangedEventArgs, avente una proprietà aggiuntiva LatestPrimeNumber.
Il metodo BuildPrimeNumberList inoltre chiama periodicamente il metodo TaskCanceled e viene terminato se viene restituito true.
' This method computes the list of prime numbers used by the ' IsPrime method. Private Function BuildPrimeNumberList( _ ByVal numberToTest As Integer, _ ByVal asyncOp As AsyncOperation) As ArrayList Dim e As ProgressChangedEventArgs = Nothing Dim primes As New ArrayList Dim firstDivisor As Integer Dim n As Integer = 5 ' Add the first prime numbers. primes.Add(2) primes.Add(3) ' Do the work. While n < numberToTest And _ Not Me.TaskCanceled(asyncOp.UserSuppliedState) If IsPrime(primes, n, firstDivisor) Then ' Report to the client that you found a prime. e = New CalculatePrimeProgressChangedEventArgs( _ n, _ CSng(n) / CSng(numberToTest) * 100, _ asyncOp.UserSuppliedState) asyncOp.Post(Me.onProgressReportDelegate, e) primes.Add(n) ' Yield the rest of this time slice. Thread.Sleep(0) End If ' Skip even numbers. n += 2 End While Return primes End Function
// This method computes the list of prime numbers used by the // IsPrime method. private ArrayList BuildPrimeNumberList( int numberToTest, AsyncOperation asyncOp) { ProgressChangedEventArgs e = null; ArrayList primes = new ArrayList(); int firstDivisor; int n = 5; // Add the first prime numbers. primes.Add(2); primes.Add(3); // Do the work. while (n < numberToTest && !TaskCanceled( asyncOp.UserSuppliedState ) ) { if (IsPrime(primes, n, out firstDivisor)) { // Report to the client that a prime was found. e = new CalculatePrimeProgressChangedEventArgs( n, (int)((float)n / (float)numberToTest * 100), asyncOp.UserSuppliedState); asyncOp.Post(this.onProgressReportDelegate, e); primes.Add(n); // Yield the rest of this time slice. Thread.Sleep(0); } // Skip even numbers. n += 2; } return primes; }
// 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 = 1; int n = 5; // Add the first prime numbers. primes.Add((Int32)2); primes.Add((Int32)3); // Do the work. while (n < numberToTest && !TaskCanceled( asyncOp.get_UserSuppliedState()) ) { if (IsPrime(primes, n, /** @out */firstDivisor)) { // Report to the client that you found a prime. e = new CalculatePrimeProgressChangedEventArgs(n, (int)((float)n / ( float)(numberToTest) * 100), asyncOp.get_UserSuppliedState()); asyncOp.Post(this.onProgressReportDelegate, e); primes.Add((Int32)n); // Yield the rest of this time slice. System.Threading.Thread.Sleep(0); } // Skip even numbers. n += 2; } return primes; }
Implementare IsPrime Tale metodo accetta tre parametri, ovvero un elenco dei numeri primi noti, il numero di cui eseguire il test e un parametro di output per il primo divisore trovato. Partendo dall'elenco di numeri primi, il metodo determina se il numero sottoposto a test ne fa parte.
' This method tests n for primality against the list of ' prime numbers contained in the primes parameter. Private Function IsPrime( _ ByVal primes As ArrayList, _ ByVal n As Integer, _ ByRef firstDivisor As Integer) As Boolean Dim foundDivisor As Boolean = False Dim exceedsSquareRoot As Boolean = False Dim i As Integer = 0 Dim divisor As Integer = 0 firstDivisor = 1 ' Stop the search if: ' there are no more primes in the list, ' there is a divisor of n in the list, or ' there is a prime that is larger than ' the square root of n. While i < primes.Count AndAlso _ Not foundDivisor AndAlso _ Not exceedsSquareRoot ' The divisor variable will be the smallest prime number ' not yet tried. divisor = primes(i) i = i + 1 ' Determine whether the divisor is greater than the ' square root of n. If divisor * divisor > n Then exceedsSquareRoot = True ' Determine whether the divisor is a factor of n. ElseIf n Mod divisor = 0 Then firstDivisor = divisor foundDivisor = True End If End While Return Not foundDivisor End Function
// This method tests n for primality against the list of // prime numbers contained in the primes parameter. private bool IsPrime( ArrayList primes, int n, out int firstDivisor) { bool foundDivisor = false; bool exceedsSquareRoot = false; int i = 0; int divisor = 0; firstDivisor = 1; // Stop the search if: // there are no more primes in the list, // there is a divisor of n in the list, or // there is a prime that is larger than // the square root of n. while ( (i < primes.Count) && !foundDivisor && !exceedsSquareRoot) { // The divisor variable will be the smallest // prime number not yet tried. divisor = (int)primes[i++]; // Determine whether the divisor is greater // than the square root of n. if (divisor * divisor > n) { exceedsSquareRoot = true; } // Determine whether the divisor is a factor of n. else if (n % divisor == 0) { firstDivisor = divisor; foundDivisor = true; } } return !foundDivisor; }
// This method tests n for primality against the list of // prime numbers contained in the primes parameter. private boolean IsPrime( ArrayList primes, int n, /** @ref */int firstDivisor) { boolean foundDivisor = false; boolean 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.get_Count() && !foundDivisor && !exceedsSquareRoot) { // The divisor variable will be the smallest // prime number not yet tried. divisor = Convert.ToInt32(primes.get_Item(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; }
DerivareCalculatePrimeProgressChangedEventArgsda ProgressChangedEventArgs. Questa classe è necessaria per la generazione di report sui risultati incrementali destinati al gestore eventi del client relativo all'evento ProgressChanged e presenta una proprietà aggiuntiva denominata LatestPrimeNumber.
Public Class CalculatePrimeProgressChangedEventArgs Inherits ProgressChangedEventArgs Private latestPrimeNumberValue As Integer = 1 Public Sub New( _ ByVal latestPrime As Integer, _ ByVal progressPercentage As Integer, _ ByVal UserState As Object) MyBase.New(progressPercentage, UserState) Me.latestPrimeNumberValue = latestPrime End Sub Public ReadOnly Property LatestPrimeNumber() As Integer Get Return latestPrimeNumberValue End Get End Property End Class
public class CalculatePrimeProgressChangedEventArgs : ProgressChangedEventArgs { private int latestPrimeNumberValue = 1; public CalculatePrimeProgressChangedEventArgs( int latestPrime, int progressPercentage, object userToken) : base( progressPercentage, userToken ) { this.latestPrimeNumberValue = latestPrime; } public int LatestPrimeNumber { get { return latestPrimeNumberValue; } } }
public class CalculatePrimeProgressChangedEventArgs extends ProgressChangedEventArgs { private int latestPrimeNumberValue = 1; public CalculatePrimeProgressChangedEventArgs(int latestPrime, int progressPercentage, Object userToken) { super(progressPercentage, userToken); this.latestPrimeNumberValue = latestPrime; } /** @property */ public int get_LatestPrimeNumber() { return latestPrimeNumberValue; } }
Verifica
A questo punto, è possibile generare il componente.
Per eseguire il test del componente
Compilare il componente.
Gli unici elementi ancora da scrivere sono i metodi di avvio e annullamento delle operazioni asincrone,CalculatePrimeAsynceCancelAsync.
Implementazione dei metodi di avvio e annullamento
Per avviare il metodo di lavoro sul relativo thread è possibile chiamare BeginInvoke sul delegato che lo contiene. Per gestire il ciclo di vita di una determinata operazione asincrona, è possibile chiamare il metodo CreateOperation sulla classe di supporto AsyncOperationManager. Viene restituito un oggetto AsyncOperation che esegue il marshalling delle chiamate sui gestori eventi del client al thread o al contesto appropriato.
Per annullare una determinata operazione in corso è possibile chiamare PostOperationCompleted sull'oggetto AsyncOperation corrispondente. In questo modo l'operazione specifica viene terminata e tutte le chiamate successive al relativo oggetto AsyncOperation generano un'eccezione.
Per implementare la funzionalità di avvio e annullamento:
Implementare il metodo CalculatePrimeAsync. Verificare che il token fornito dal client (ID attività) sia univoco rispetto a tutti i token che rappresentano le attività in sospeso. Se il client passa un token non univoco, CalculatePrimeAsync genera un'eccezione. In caso contrario, il token viene aggiunto all'insieme 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 Overridable Sub CalculatePrimeAsync( _ ByVal numberToTest As Integer, _ ByVal taskId As Object) ' Create an AsyncOperation for taskId. Dim asyncOp As AsyncOperation = _ AsyncOperationManager.CreateOperation(taskId) ' Multiple threads will access the task dictionary, ' so it must be locked to serialize access. SyncLock userStateToLifetime.SyncRoot If userStateToLifetime.Contains(taskId) Then Throw New ArgumentException( _ "Task ID parameter must be unique", _ "taskId") End If userStateToLifetime(taskId) = asyncOp End SyncLock ' Start the asynchronous operation. Dim workerDelegate As New WorkerEventHandler( _ AddressOf CalculateWorker) workerDelegate.BeginInvoke( _ numberToTest, _ asyncOp, _ Nothing, _ Nothing) End Sub
// This method starts an asynchronous calculation. // First, it checks the supplied task ID for uniqueness. // If taskId is unique, it creates a new WorkerEventHandler // and calls its BeginInvoke method to start the calculation. public virtual void CalculatePrimeAsync( int numberToTest, object taskId) { // Create an AsyncOperation for taskId. AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(taskId); // Multiple threads will access the task dictionary, // so it must be locked to serialize access. lock (userStateToLifetime.SyncRoot) { if (userStateToLifetime.Contains(taskId)) { throw new ArgumentException( "Task ID parameter must be unique", "taskId"); } userStateToLifetime[taskId] = asyncOp; } // Start the asynchronous operation. WorkerEventHandler workerDelegate = new WorkerEventHandler(CalculateWorker); workerDelegate.BeginInvoke( numberToTest, asyncOp, null, null); }
// 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 void CalculatePrimeAsync(int numberToTest, Object taskId) { // Create an AsyncOperation for taskId. AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(taskId); synchronized (userStateToLifetime.get_SyncRoot()) { // Multiple threads will access the task dictionary, // so it must be locked to serialize access. if (userStateToLifetime.Contains(taskId)) { throw new ArgumentException("Task ID parameter must be unique", "taskId"); } userStateToLifetime.set_Item(taskId, asyncOp); } // Start the asynchronous operation. WorkerEventHandler workerDelegate = new WorkerEventHandler(CalculateWorker); workerDelegate.BeginInvoke( numberToTest, asyncOp, null, null); }
Implementare il metodo CancelAsync. Se nell'insieme dei token è presente il parametro taskId, verrà rimosso. In questo modo viene impedita l'esecuzione delle attività annullate non ancora avviate. Se l'attività è in esecuzione, il metodo BuildPrimeNumberList viene terminato quando rileva che l'ID attività è stato rimosso dall'insieme dei cicli di vita.
' This method cancels a pending asynchronous operation. Public Sub CancelAsync(ByVal taskId As Object) Dim obj As Object = userStateToLifetime(taskId) If (obj IsNot Nothing) Then SyncLock userStateToLifetime.SyncRoot userStateToLifetime.Remove(taskId) End SyncLock End If End Sub
// This method cancels a pending asynchronous operation. public void CancelAsync(object taskId) { AsyncOperation asyncOp = userStateToLifetime[taskId] as AsyncOperation; if (asyncOp != null) { lock (userStateToLifetime.SyncRoot) { userStateToLifetime.Remove(taskId); } } }
// This method cancels a pending asynchronous operation. public void CancelAsync(Object taskId) { Object obj = userStateToLifetime.get_Item(taskId); if (obj != null) { synchronized (userStateToLifetime.get_SyncRoot()) { userStateToLifetime.Remove(taskId); } } }
Verifica
A questo punto, è possibile generare il componente.
Per eseguire il test del componente
- Compilare il componente.
A questo punto, il componente PrimeNumberCalculator è completo e può essere utilizzato.
Per un client di esempio che utilizzi il componente PrimeNumberCalculator, vedere Procedura: implementare un client del modello asincrono basato su eventi.
Passaggi successivi
Per completare l'esempio è possibile scrivere CalculatePrime, l'equivalente sincrono del metodo CalculatePrimeAsync. In questo modo il componente PrimeNumberCalculator verrà reso completamente compatibile con il modello asincrono basato su eventi.
Per migliorare l'esempio è possibile conservare l'elenco di tutti i numeri primi rilevati durante i vari richiami dei diversi numeri sottoposti a test. Con questo approccio ogni attività potrà usufruire dei vantaggi derivanti dalle attività già eseguite. Proteggere l'elenco con le aree lock in modo da serializzarne l'accesso da parte dei diversi thread.
Per migliorare ulteriormente l'esempio è possibile eseguire il test di divisori triviali come 2, 3 e 5.
Vedere anche
Attività
Procedura: eseguire un'operazione in background
Procedura: implementare un componente che supporta il modello asincrono basato su eventi
Concetti
Cenni preliminari sul modello asincrono basato su eventi
Altre risorse
Multithreading in Visual Basic
Programmazione multithreading con il modello asincrono basato su eventi