Share via


Tutorial: Implementar un componente que admita el modelo asincrónico basado en eventos

Actualización: noviembre 2007

Si está escribiendo una clase con algunas operaciones que puedan incurrir en retrasos notables, puede probar a darle funcionalidad asincrónica implementando Información general sobre el modelo asincrónico basado en eventos.

Este tutorial muestra cómo crear un componente que implemente el modelo asincrónico basado en eventos. Se implementa utilizando clases de ayuda del espacio de nombres System.ComponentModel, lo que garantiza que el componente funciona correctamente bajo cualquier modelo de aplicación, incluso ASP.NET, aplicaciones de consola y aplicaciones de Windows Forms. Este componente también se puede diseñar con un control PropertyGrid y con diseñadores personalizados propios.

Cuando haya terminado, tendrá una aplicación que calcula de forma asincrónica los números primos. La aplicación tendrá un subproceso de interfaz de usuario principal y un subproceso para cada cálculo de número primo. Aunque comprobar si un número elevado es primo puede llevar una cantidad de tiempo considerable, el subproceso de interfaz de usuario principal no se verá interrumpido por este retraso, y el formulario permanecerá receptivo durante el cálculo. Podrá ejecutar tantos cálculos como desee simultáneamente, así como cancelar de forma selectiva los cálculos pendientes.

Entre las tareas ilustradas en este tutorial se 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 un listado sencillo, vea Cómo: Implementar un componente que admita el 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 denominada PrimeNumberCalculator que herede de Component.

Definir delegados y eventos asincrónicos públicos

Un componente se comunica con los clientes mediante eventos. El evento MethodNameCompleted avisa a los clientes de la finalización de una tarea asincrónica y el evento MethodNameProgressChanged informa a los clientes sobre el progreso de dicha tarea.

Para definir eventos asincrónicos para los clientes de un componente:

  1. Importe los espacios de nombres System.Threading y System.Collections.Specialized que están en la parte superior del archivo.

    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.*;
    
    
  2. Antes de la definición de la clase PrimeNumberCalculator , declare delegados para los eventos de progreso y finalización.

    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);
    
  3. En la definición de la clase PrimeNumberCalculator , declare los eventos para informar a los clientes sobre el progreso y la finalización de la tarea.

    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);
    } 
    
  4. Después de la definición de la clase PrimeNumberCalculator , derive la clase CalculatePrimeCompletedEventArgs para informar del resultado de cada cálculo al controlador de eventos del cliente correspondiente al evento CalculatePrimeCompleted. Además de las propiedades AsyncCompletedEventArgs, esta clase permite al cliente determinar qué numero se ha comprobado, si es primo y, en caso de que no lo sea, cuál es el primer divisor.

    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;
        } 
    } 
    

Punto de control

En este punto, ya puede generar 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 tome 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 el comportamiento asincrónico interno de un componente:

  1. Declare y cree los delegados SendOrPostCallback en la clase PrimeNumberCalculator . Cree los objetos SendOrPostCallback en un método de utilidad denominado InitializeDelegates.

    Necesitará dos delegados: uno para informar sobre el progreso al cliente y otro para informar sobre la finalización al cliente.

    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);
    } 
    
    
  2. Llame al método InitializeDelegates en el constructor 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();
    } 
    
  3. Declare un delegado en la clase PrimeNumberCalculator que controla el trabajo real que se va a hacer 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 utilizará para realizar el seguimiento de la duración de la operación asincrónica.

    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 );
    
  4. Cree una colección para administrar la duración de las operaciones asincrónicas pendientes. El cliente necesita de alguna manera realizar un seguimiento de la ejecución y finalización de las operaciones. Dicho seguimiento se realiza pidiéndole al cliente que pase un símbolo (token) único, o identificador de tarea, cuando realice la llamada al método asincrónico. El componente PrimeNumberCalculator debe mantener un registro de cada llamada asociando el identificador de tarea a su invocación correspondiente. Si el cliente pasa un identificador de tarea que no es único, el componente PrimeNumberCalculator debe provocar una excepción.

    El componentePrimeNumberCalculatormantiene un registro del identificador de tarea utilizando una clase de colección especial denominada HybridDictionary. En la definición de clase, cree un objeto HybridDictionary denominado userTokenToLifetime.

    Private userStateToLifetime As New HybridDictionary()
    
    private HybridDictionary userStateToLifetime = 
        new HybridDictionary();
    
    private HybridDictionary userStateToLifetime = 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 apropiado con la ayuda de la clase AsyncOperation.

Para provocar eventos en los clientes de un componente:

  • Implemente eventos públicos para informar a los clientes. Necesitará un evento para informar sobre el 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 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);
        }
    } 
    

Implementar el método de finalización

El delegado de finalización es el método que invocará el comportamiento asincrónico de subprocesamiento libre subyacente una vez que finalice la operación asincrónica, bien porque ésta haya acabado con éxito, porque se haya producido un error o por cancelación. Esta invocación se produce en un subproceso arbitrario.

En este método es donde se quita el identificador de tarea del cliente de la colección interna de símbolos (token) de cliente únicos. Este método también pone término a la duración de una operación asincrónica determinada llamando al método PostOperationCompleted en el objeto AsyncOperation correspondiente. Esta llamada provoca en el subproceso el evento de finalización adecuado para el modelo de aplicación. Una vez llamado el método PostOperationCompleted, no es posible seguir utilizando esta instancia de AsyncOperation. Cualquier intento posterior de utilizarla producirá una excepción.

La firma CompletionMethod debe contener toda la información de estado necesaria para describir el resultado de la operación asincrónica. Contiene información de estado del número comprobado por esta operación asincrónica específica, si el número es primo, y el valor de su primer divisor, si el número no es primo. También contiene información de estado que describe cualquier excepción que se haya producido y el objeto AsyncOperation correspondiente a esta tarea determinada.

Para poner término a una operación asincrónica:

  • Implemente el método de finalización. Toma seis parámetros, que utiliza para rellenar CalculatePrimeCompletedEventArgs que se devuelve al cliente a través de CalculatePrimeCompletedEventHandler del cliente. Quita el símbolo (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 al método PostOperationCompleted. AsyncOperation calcula las referencias de la llamada al subproceso o contexto adecuado 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 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.
    

Punto de control

En este punto, ya puede generar 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 el momento, ha implementado el código asincrónico admitido para el componentePrimeNumberCalculator. Ahora puede implementar el código que hace el trabajo real. Implementará tres métodos:CalculateWorker, BuildPrimeNumberList eIsPrime. Juntos,BuildPrimeNumberList eIsPrimeconstituyen 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 probado. Si llegados a ese punto no se ha encontrado ningún divisor, el número en cuestión es primo.

Si este componente fuese escrito para obtener una eficacia máxima, recordaría todos los números primos descubiertos por distintas invocaciones para los distintos números probados. También buscaría divisores triviales como el 2, el 3 y el 5. Sea como sea, con este ejemplo se pretende demostrar lo largas que pueden resultar las operaciones ejecutadas de forma asincrónica, por lo que estas optimizaciones se dejan como ejercicio para quien las quiera realizar.

El método CalculateWorker se ajusta en un delegado y se invoca de forma asincrónica con una llamada a BeginInvoke.

Nota:

La información del progreso se implementa en el método BuildPrimeNumberList. En equipos rápidos, se pueden provocar eventos ProgressChanged en una sucesión rápida. El subproceso del cliente, en el que se producen estos eventos, debe ser capaz de controlar esta situación. El código de interfaz de usuario puede verse inundado de mensajes y es posible que no pueda seguir el ritmo, lo que provoca un comportamiento de bloqueo. Para obtener un ejemplo de interfaz de usuario que controla esta situación, vea Cómo: Implementar un cliente en un modelo asincrónico basado en eventos.

Para ejecutar de forma asincrónica el cálculo de números primos:

  1. Implemente el método de utilidad TaskCanceled. Esto comprueba la colección de duración de tarea para el identificador de tarea determinado y devuelve true si no encuentra el identificador de tarea.

    ' 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) );
        }
    
  2. Implemente el método CalculateWorker. Toma dos parámetros: un número que se va a comprobar y un objeto 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);
        }
    
  3. ImplementeBuildPrimeNumberList. Toma dos parámetros: el número que se va a comprobar y un objeto AsyncOperation. AsyncOperation se utiliza para informar sobre el progreso y los resultados incrementales. Esto garantiza que se llame a los controladores de eventos del cliente en el contexto o subproceso apropiados para el modelo de aplicación. Cuando BuildPrimeNumberList encuentra un número primo, informa que éste es el resultado incremental al controlador de eventos del cliente correspondiente al evento ProgressChanged. Esto requiere una clase derivada de ProgressChangedEventArgs, llamada CalculatePrimeProgressChangedEventArgs, que tiene una propiedad agregada llamada LatestPrimeNumber.

    El método BuildPrimeNumberList también llama periódicamente al método TaskCanceled y se cierra si el método devuelve 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;
        } 
    
  4. ImplementeIsPrime. Toma tres parámetros: una lista de números primos conocidos, el número que se quiere comprobar y un parámetro de salida para el primer divisor encontrado. Dada la lista de números primos, determina si el número que se está comprobando es primo.

    ' 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;
        } 
    
  5. DeriveCalculatePrimeProgressChangedEventArgsde ProgressChangedEventArgs. Esta clase es necesaria para informar sobre los resultados incrementales al controlador de eventos del cliente correspondiente al evento ProgressChanged. Tiene una propiedad adicional denominada 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;
        } 
    }
    

Punto de control

En este punto, ya puede generar el componente.

Para probar el componente

  • Compile el componente.

    Lo único que falta por escribir son los métodos para iniciar y cancelar operaciones asincrónicas,CalculatePrimeAsyncyCancelAsync.

Implementar 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 incluye. Para administrar la duración de una operación asincrónica determinada, llame al método CreateOperation en la clase de ayuda AsyncOperationManager. Esto devuelve un objeto AsyncOperation, que calcula las referencias de las llamadas en los controladores de eventos del cliente para el contexto o subproceso apropiado.

Puede cancelar una operación pendiente determinada llamando a PostOperationCompleted en su AsyncOperation correspondiente. Esto finaliza esa operación y cualquier llamada subsiguiente a su AsyncOperation producirá una excepción.

Para implementar la funcionalidad de inicio y cancelación:

  1. Implemente el método CalculatePrimeAsync. Asegúrese de que el símbolo (token) o identificador de tarea proporcionado por el cliente es único, frente a todos los símbolos (token) que representan tareas pendientes en ese momento. Si el cliente pasa un símbolo (token) que no es único, CalculatePrimeAsync produce una excepción. De lo contrario, el símbolo (token) se agrega a la colección de identificadores 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 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);
        } 
    
  2. Implemente el método CancelAsync. Si existe el parámetro taskId en la colección de símbolos (token), se elimina. Esto evita la ejecución de las tareas canceladas que no han comenzado. Si la tarea está ejecutándose, el método BuildPrimeNumberList se cierra cuando detecta que se ha eliminado el identificador de tarea de la colección de duración.

    ' 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);
                }
            }
        }
    

Punto de control

En este punto, ya puede generar el componente.

Para probar el componente

  • Compile el componente.

El componente PrimeNumberCalculator ya está completo y listo para su uso.

Para obtener un ejemplo de cliente que utilice el componente PrimeNumberCalculator, vea Cómo: Implementar un cliente en un modelo asincrónico basado en eventos.

Pasos siguientes

Puede completar este ejemplo escribiendo CalculatePrime, el equivalente sincrónico del método CalculatePrimeAsync. Esto hará que el componente PrimeNumberCalculator cumpla plenamente las especificaciones del modelo asincrónico basado en eventos.

Puede mejorar este ejemplo conservando la lista de todos los números primos descubiertos por las distintas invocaciones de los distintos números probados. Si se usa este procedimiento, cada tarea se beneficiará del trabajo realizado por tareas anteriores. Recuerde proteger esta lista con regiones lock, de manera que se serialice el acceso a la lista por parte de subprocesos diferentes.

También puede mejorar este ejemplo probando divisores triviales, como el 2, el 3 y el 5.

Vea también

Tareas

Cómo: Ejecutar una operación en segundo plano

Cómo: Implementar un componente que admita el modelo asincrónico basado en eventos

Conceptos

Información general sobre el modelo asincrónico basado en eventos

Otros recursos

Subprocesamiento múltiple en Visual Basic

Programación multiproceso con el modelo asincrónico basado en eventos