Procedimiento para implementar un componente que admita el modelo asincrónico basado en eventos
Si está escribiendo una clase con algunas operaciones que pueden dar lugar a retrasos evidentes, considere la posibilidad de darle funcionalidad asincrónica implementando Información general sobre el modelo asincrónico basado en eventos.
En este tutorial se explica cómo crear un componente que implementa el modelo asincrónico basado en eventos. Se implementa mediante clases auxiliares del espacio de nombres System.ComponentModel, lo que garantiza que el componente funcione correctamente en cualquier modelo de aplicación, incluidas aplicaciones de ASP.NET, de consola y de Windows Forms. Este componente también se puede diseñar con un control PropertyGrid y sus propios diseñadores personalizados.
Cuando haya terminado, tendrá una aplicación que calcula de forma asincrónica los números de primos. La aplicación tendrá un subproceso de interfaz de usuario principal y un subproceso para cada cálculo de números primos. Aunque comproar si un número elevado es primo puede llevar un tiempo considerable, el subproceso de interfaz de usuario principal no se interrumpirá por este retraso, y el formulario seguirá respondiendo durante el cálculo. Podrá ejecutar tantos cálculos como cálculos simultáneos de cancelación desee de forma simultánea y selectiva.
Las tareas ilustradas en este tutorial incluyen:
Crear el componente
Definir delegados y eventos asincrónicos públicos
Definir delegados privados
Implementar eventos públicos
Implementar el método de finalización
Implementar los métodos de trabajo
Implementar métodos de inicio y cancelación
Para copiar el código de este tema como una sola lista, vea Cómo: Implementar un cliente en un modelo asincrónico basado en eventos.
Crear el componente
El primer paso es crear el componente que implementará el modelo asincrónico basado en eventos.
Para crear el componente
- Cree una clase llamada
PrimeNumberCalculator
y heredada de Component.
Definir delegados y eventos asincrónicos públicos
El componente se comunica con los clientes mediante eventos. El evento MethodNameCompleted informa a los clientes de la finalización de una tarea asincrónica, y el evento MethodNameProgressChanged informa a los clientes del progreso de una tarea asincrónica.
Para definir eventos asincrónicos para los clientes de su componente:
Importe los espacios de nombres System.Threading y System.Collections.Specialized en la parte superior del archivo.
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
Antes de la definición de clase
PrimeNumberCalculator
, declare delegados de progreso y eventos de finalización.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)
Antes de la definición de clase
PrimeNumberCalculator
, declare eventos de progreso de los informes y de finalización para los clientes.public event ProgressChangedEventHandler ProgressChanged; public event CalculatePrimeCompletedEventHandler CalculatePrimeCompleted;
Public Event ProgressChanged _ As ProgressChangedEventHandler Public Event CalculatePrimeCompleted _ As CalculatePrimeCompletedEventHandler
Después de la definición de clase
PrimeNumberCalculator
, derive la claseCalculatePrimeCompletedEventArgs
para generar informes del resultado de cada cálculo en el controlador de eventos del cliente para el eventoCalculatePrimeCompleted
. Además de las propiedadesAsyncCompletedEventArgs
, esta clase permite al cliente determinar qué número se probó, si es primo y cuál es el primer divisor en caso de que no sea 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
Punto de comprobación 1
En este momento, puede compilar el componente.
Para probar el componente
Compile el componente.
Recibirá dos advertencias del compilador:
warning CS0067: The event 'AsynchronousPatternExample.PrimeNumberCalculator.ProgressChanged' is never used warning CS0067: The event 'AsynchronousPatternExample.PrimeNumberCalculator.CalculatePrimeCompleted' is never used
Estas advertencias se borrarán en la sección siguiente.
Definir delegados privados
Los aspectos asincrónicos del componente PrimeNumberCalculator
se implementan internamente con un delegado especial conocido como SendOrPostCallback. SendOrPostCallback representa un método de devolución de llamada que se ejecuta en un subproceso ThreadPool. El método de devolución de llamada debe tener una firma que toma un único parámetro de tipo Object, lo que significa que tendrá que pasar información de estado entre los delegados de una clase contenedora. Para obtener más información, vea SendOrPostCallback.
Para implementar un comportamiento asincrónico interno del componente:
Declare y cree los delegados SendOrPostCallback en la clase
PrimeNumberCalculator
. Cree los objetos SendOrPostCallback en un método de utilidad llamadoInitializeDelegates
.Necesitará dos delegados: uno para notificar el progreso al cliente y otro para informar sobre la finalización al 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
Llame al método
InitializeDelegates
en el constructor del componente.public PrimeNumberCalculator() { InitializeComponent(); InitializeDelegates(); }
Public Sub New() InitializeComponent() InitializeDelegates() End Sub
Declare un delegado en la clase
PrimeNumberCalculator
que controla el trabajo real para que se realice de forma asincrónica. Este delegado ajusta el método de trabajo que comprueba si un número es primo. El delegado toma un parámetro AsyncOperation, que se usará para realizar el seguimiento de la duración de la operación asincrónica.private delegate void WorkerEventHandler( int numberToCheck, AsyncOperation asyncOp);
Private Delegate Sub WorkerEventHandler( _ ByVal numberToCheck As Integer, _ ByVal asyncOp As AsyncOperation)
Cree una colección para administrar la duración de las operaciones asincrónicas pendientes. El cliente necesita una forma de realizar el seguimiento de las operaciones a medida que se ejecutan y completan, y este seguimiento se realiza requiriendo al cliente que pase un token exclusivo o un identificador de tarea cuando el cliente realiza la llamada al método asincrónico. El componente
PrimeNumberCalculator
debe realizar un seguimiento de cada llamada mediante la asociación del identificador de tarea con su invocación correspondiente. Si el cliente pasa un identificador de tarea que no es único, el componentePrimeNumberCalculator
debe generar una excepción.El componente
PrimeNumberCalculator
realiza un seguimiento del identificador de tarea con una clase de colección especial llamada HybridDictionary. En la definición de clase, cree una clase HybridDictionary denominadauserStateToLifetime
.private HybridDictionary userStateToLifetime = new HybridDictionary();
Private userStateToLifetime As New HybridDictionary()
Implementar eventos públicos
Los componentes que implementan el modelo asincrónico basado en eventos se comunican con los clientes mediante eventos. Estos eventos se invocan en el subproceso adecuado con la ayuda de la clase AsyncOperation.
Para generar eventos en los clientes del componente:
Implemente eventos públicos para informar a los clientes. Necesitará un evento para los informes de progreso y otro para informar de la finalización.
// 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 el método de finalización
El delegado de finalización es el método que el comportamiento asincrónico de subproceso libre y subyacente invocará cuando la operación asincrónica finaliza tras una terminación correcta, un error o una cancelación. Esta invocación se produce en un subproceso arbitrario.
Este método es donde se quita el identificador de tarea del cliente de la colección interna de tokens de clientes exclusivos. Este método también finaliza la duración de una operación asincrónica determinada llamando al método PostOperationCompleted en la clase AsyncOperation correspondiente. Esta llamada genera el evento de finalización en el subproceso que es apropiado para el modelo de aplicación. Después de llamar al método PostOperationCompleted, ya no se puede usar la instancia de AsyncOperation, y todos los intentos posteriores para usarla generarán una excepción.
La firma CompletionMethod
debe contener todos los estados necesarios para describir el resultado de la operación asincrónica. Contiene información de estado del número que probó esta operación asincrónica particular, si el número es primo y el valor de su primer divisor en caso de que no se trate de un número primo. También contiene información de estado que describe cualquier excepción generada y la clase AsyncOperation correspondiente a esta tarea concreta.
Para completar una operación asincrónica:
Implemente el método de finalización. Adopta seis parámetros, que se usan para rellenar un objeto
CalculatePrimeCompletedEventArgs
devuelto al cliente mediante el objetoCalculatePrimeCompletedEventHandler
del cliente. Quita el token del identificador de tarea del cliente de la colección interna y finaliza la duración de la operación asincrónica con una llamada a PostOperationCompleted. AsyncOperation serializa la llamada al subproceso o contexto que son apropiados para el modelo de aplicación.// 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
Punto de comprobación 2
En este momento, puede compilar el componente.
Para probar el componente
Compile el componente.
Recibirá una advertencia del compilador:
warning CS0169: The private field 'AsynchronousPatternExample.PrimeNumberCalculator.workerDelegate' is never used
Esta advertencia se resolverá en la sección siguiente.
Implementar los métodos de trabajo
Hasta ahora, ha implementado el código asincrónico admitido para el componente PrimeNumberCalculator
. Ahora puede implementar el código que realiza el trabajo real. Implementará tres métodos: CalculateWorker
, BuildPrimeNumberList
y IsPrime
. Juntos, BuildPrimeNumberList
y IsPrime
constituyen un algoritmo muy conocido denominado criba de Eratóstenes, que determina si un número es primo buscando todos los números primos hasta la raíz cuadrada del número de prueba. Si no se encuentran divisores en este punto, el número de prueba es primo.
Si este componente se hubiera escrito para obtener la máxima eficacia, recordaría todos los números primos detectados por distintas invocaciones para diferentes números de prueba. También buscaría divisores triviales como 2, 3 y 5. Con este ejemplo se pretende demostrar cómo las operaciones que consumen mucho tiempo pueden ejecutarse de forma asincrónica; sin embargo, estas optimizaciones se dejan como un ejercicio para el usuario.
El método CalculateWorker
se ajusta en un delegado y se invoca de forma asincrónica con una llamada a BeginInvoke
.
Nota
Los informes de progreso se implementan en el método BuildPrimeNumberList
. En equipos rápidos, los eventos ProgressChanged
se pueden producir en una sucesión rápida. El subproceso de cliente, en el que se producen estos eventos, debe ser capaz de controlar esta situación. El código de la interfaz de usuario puede estar congestionado con mensajes y no ser capaz de afrontar esta situación, de tal manera que se puede producir una falta de respuesta. Para consultar una interfaz de usuario de ejemplo que controla esta situación, vea Implementar un cliente en un modelo asincrónico basado en eventos.
Para ejecutar de forma asincrónica el cálculo de números primos:
Implemente el método de utilidad
TaskCanceled
. Esto comprueba la colección de la duración de la tarea de un identificador de tarea concreto y devuelvetrue
si no se encuentra el identificador de tarea.// 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
Implemente el método
CalculateWorker
. Adopta dos parámetros: un número para probar y 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
Implemente
BuildPrimeNumberList
. Adopta dos parámetros: el número para probar y AsyncOperation. Utiliza AsyncOperation para informar del progreso y de los resultados incrementales. Esto garantiza que se llama a los controladores de eventos del cliente en el subproceso o contexto apropiados para el modelo de aplicación. CuandoBuildPrimeNumberList
encuentra un número primo, informa de este como un resultado incremental al controlador de eventos del cliente para el eventoProgressChanged
. Esto requiere una clase derivada de ProgressChangedEventArgs, llamadaCalculatePrimeProgressChangedEventArgs
, que tiene una propiedad agregada denominadaLatestPrimeNumber
.El método
BuildPrimeNumberList
también llama periódicamente al métodoTaskCanceled
y se cierra si el método devuelvetrue
.// 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
Implemente
IsPrime
. Adopta tres parámetros: una lista de números primos conocidos, el número que se va a probar y un parámetro de salida para el primer divisor encontrado. Dada la lista de números primos, determina si el número de prueba es 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
Deriva
CalculatePrimeProgressChangedEventArgs
de ProgressChangedEventArgs. Esta clase es necesaria para notificar los resultados incrementales al controlador de eventos del cliente para el eventoProgressChanged
. Tiene una propiedad agregada denominadaLatestPrimeNumber
.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
Punto de comprobación 3
En este momento, puede compilar el componente.
Para probar el componente
Compile el componente.
Todo lo que queda por escribir son los métodos para iniciar y cancelar operaciones asincrónicas,
CalculatePrimeAsync
yCancelAsync
.
Implementación de los métodos de inicio y cancelación
Inicie el método de trabajo en su propio subproceso llamando a BeginInvoke
en el delegado que lo contiene. Para administrar la duración de una operación asincrónica determinada, llame al método CreateOperation en la clase del asistente AsyncOperationManager. Devuelve AsyncOperation, que serializa las llamadas a los controladores de eventos del cliente para el subproceso o contexto apropiados.
Puede cancelar una operación pendiente concreta si llama a PostOperationCompleted en su clase AsyncOperation correspondiente. Esto finaliza esa operación y todas las llamadas posteriores a AsyncOperation generarán una excepción.
Para implementar la funcionalidad de inicio y cancelación:
Implemente el método
CalculatePrimeAsync
. Asegúrese de que el token proporcionado por el cliente (identificador de tarea) es único con respecto a todos los tokens que representan actualmente las tareas pendientes. Si el cliente pasa un token no exclusivo,CalculatePrimeAsync
genera una excepción. En caso contrario, el token se agrega a la colección de identificador de tarea.// 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
Implemente el método
CancelAsync
. Si el parámetrotaskId
existe en la colección de token, se elimina. Esto impide que las tareas canceladas no empiecen a ejecutarse. Si la tarea está en ejecución, el métodoBuildPrimeNumberList
se cierra cuando detecta que el identificador de tarea se ha quitado de la colección de duración.// 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
Punto de comprobación 4
En este momento, puede compilar el componente.
Para probar el componente
- Compile el componente.
El componente PrimeNumberCalculator
ya está completo y listo para usarse.
Para un cliente de ejemplo que usa el componente PrimeNumberCalculator
, consulte Cómo: Implementar un cliente del modelo asincrónico basado en eventos.
Pasos siguientes
Puede rellenar este ejemplo escribiendo CalculatePrime
, el equivalente sincrónico del método CalculatePrimeAsync
. Esto hará que el componente PrimeNumberCalculator
sea totalmente compatible con el modelo asincrónico basado en eventos.
Puede mejorar este ejemplo conservando la lista de todos los números primos detectados por varias invocaciones de distintos números de prueba. Con este enfoque, cada tarea se beneficiará del trabajo realizado por tareas anteriores. Tenga la precaución de proteger esta lista con las regiones lock
, para que el acceso a la lista por parte de diferentes subprocesos se serialice.
También puede mejorar este ejemplo probando divisores triviales, como 2, 3 y 5.
Consulte también
- Cómo: Ejecutar una operación en segundo plano
- Event-based Asynchronous Pattern Overview (Información general sobre el modelo asincrónico basado en eventos)
- Modelo asincrónico basado en eventos (EAP)