Implementar 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.

El modelo asincrónico basado en eventos constituye una forma estandarizada de empaquetar clases con características asincrónicas. Si se implementa con clases de ayuda como AsyncOperationManager, la clase funcionará correctamente bajo cualquier modelo de aplicación, incluidas ASP.NET, aplicaciones de consola y aplicaciones de Windows Forms.

Para ver un ejemplo en el que se implementa el modelo asincrónico basado en eventos, vea Cómo: Implementar un componente que admita el modelo asincrónico basado en eventos.

Para las operaciones asincrónicas simples, el componente BackgroundWorker puede resultar apropiado. Para obtener más información acerca de BackgroundWorker, vea Cómo: Ejecutar una operación en segundo plano.

En la lista siguiente se describen las características del modelo asincrónico basado en eventos que se tratan en este tema.

  • Posibilidad de implementar el modelo asincrónico basado en eventos

  • Asignación de nombres a los métodos asincrónicos

  • Admisión opcional de la cancelación

  • Admisión opcional de la propiedad IsBusy

  • Compatibilidad opcional con la información de progreso

  • Compatibilidad opcional con la devolución de resultados incrementales

  • Uso de los parámetros Out y Ref en métodos

Posibilidad de implementar el modelo asincrónico basado en eventos

Considere la posibilidad de implementar el modelo asincrónico basado en eventos en los siguientes casos:

  • Cuando los clientes de una clase no necesiten objetos WaitHandle y IAsyncResult para operaciones asincrónicas, lo que significa que el sondeo y WaitAll o WaitAny tendrán que ser generados por el cliente.

  • Cuando desee que las operaciones asincrónicas sean administradas por el cliente con el modelo conocido de evento/delegado.

Cualquier operación puede ser objeto de una implementación asincrónica, si bien deberían considerarse de manera especial aquéllas cuya latencia pudiera ser más larga. Son especialmente adecuadas las operaciones en las que los clientes llaman a un método y se les envía una notificación cuando finaliza, sin que sea necesaria ninguna otra intervención. También son adecuadas las operaciones que se ejecutan de forma continua, y que avisan periódicamente a los clientes sobre el progreso, los resultados incrementales o los cambios de estado.

Para obtener más información sobre la decisión de cuándo admitir el modelo asincrónico basado en eventos, vea Decidir cuándo implementar el modelo asincrónico basado en eventos.

Asignación de nombres a los métodos asincrónicos

Para cada método nombreDeMétodo sincrónico para el que quiera proporcionar un homólogo asincrónico:

Defina un método nombreDeMétodoAsync que:

  • Devuelva void.

  • Tome los mismos parámetros que el método nombreDeMétodo.

  • Acepte varias invocaciones.

Si lo desea, también puede definir una sobrecarga nombreDeMétodoAsync, idéntica a nombreDeMétodoAsync, pero con un parámetro de valor de objeto adicional denominado userState. Haga esto si está preparado para administrar varias invocaciones simultáneas de su método, en cuyo caso se devolverá el valor userState a todos los controladores de eventos para diferenciar las invocaciones del método. También puede hacerlo simplemente para crear un lugar donde almacenar el estado del usuario para su posterior recuperación.

Para cada firma de método nombreDeMétodoAsync independiente:

  1. Defina el evento siguiente en la misma clase que el método:

    Public Event MethodNameCompleted As MethodNameCompletedEventHandler
    
    public event MethodNameCompletedEventHandler MethodNameCompleted;
    
  2. Defina el delegado siguiente y AsyncCompletedEventArgs. Éstos probablemente se definirán fuera de la propia clase, pero en el mismo espacio de nombres.

    Public Delegate Sub MethodNameCompletedEventHandler( _
        ByVal sender As Object, _
        ByVal e As MethodNameCompletedEventArgs)
    
    Public Class MethodNameCompletedEventArgs
        Inherits System.ComponentModel.AsyncCompletedEventArgs
    Public ReadOnly Property Result() As MyReturnType
    End Property
    
    public delegate void MethodNameCompletedEventHandler(object sender, 
        MethodNameCompletedEventArgs e);
    
    public class MethodNameCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs
    {
        public MyReturnType Result { get; }
    }
    
    • Asegúrese de que la clase nombreDeMétodoCompletedEventArgs expone sus miembros como propiedades de sólo lectura, y no como campos, ya que los campos impiden el enlace de datos.

    • No defina ninguna clase derivada de AsyncCompletedEventArgs para métodos que no generan resultados. Simplemente utilice una instancia del propio AsyncCompletedEventArgs.

      Nota:

      Es perfectamente aceptable, siempre que sea factible y adecuado, reutilizar tipos AsyncCompletedEventArgs y de delegado. En este caso, los nombres asignados no serán necesariamente coherentes con el nombre del método, ya que ni el delegado ni AsyncCompletedEventArgs estarán asociados a un único método.

Admisión opcional de la cancelación

Si una clase va a admitir la cancelación de operaciones asincrónicas, dicha cancelación se debería exponer al cliente tal y como se describe a continuación. Observe que deben resolverse dos cuestiones antes de definir si se admite la cancelación:

  • ¿Tiene la clase, incluida cualquier futura adición que se pueda anticipar, una única operación asincrónica que admita la cancelación?

  • ¿Son compatibles las operaciones asincrónicas que admiten la cancelación con la existencia de varias operaciones pendientes? Es decir, ¿puede tomar el método nombreDeMétodoAsync un parámetro userState y permitir varias invocaciones sin necesidad de esperar a que finalice cualquiera de ellas?

Busque las respuestas a estas dos preguntas en la tabla siguiente para determinar cuál debería ser la firma para el método de cancelación.

Visual Basic

 

Admisión de varias operaciones simultáneas

Sólo una operación cada vez

Una operación asincrónica en toda la clase

Sub MethodNameAsyncCancel(ByVal userState As Object)
Sub MethodNameAsyncCancel()

Varias operaciones asincrónicas en la clase

Sub CancelAsync(ByVal userState As Object)
Sub CancelAsync()

C#

 

Admisión de varias operaciones simultáneas

Sólo una operación cada vez

Una operación asincrónica en toda la clase

void MethodNameAsyncCancel(object userState);
void MethodNameAsyncCancel();

Varias operaciones asincrónicas en la clase

void CancelAsync(object userState);
void CancelAsync();

Si define el método CancelAsync(object userState) , los clientes deberán tener la precaución, a la hora de escoger sus valores de estado, de hacer que éstos sean capaces de diferenciar entre todos los métodos asincrónicos invocados en el objeto, y no sólo entre todas las invocaciones de un único método asincrónico.

La decisión de denominar a la versión de la operación asincrónica única como nombreDeMétodoAsyncCancel se basa en la capacidad de detectar el método con mayor facilidad en un entorno de diseño como IntelliSense, de Visual Studio. Esto agrupa los miembros relacionados y los distingue de otros miembros que no tienen nada que ver con la funcionalidad asincrónica. Si espera que se puedan agregar operaciones asincrónicas adicionales en versiones subsiguientes, es mejor definir CancelAsync.

No defina varios métodos de la tabla anterior en la misma clase. Eso no tendrá sentido, o recargará la interfaz de clase con una proliferación de métodos.

Normalmente, estos métodos devolverán un resultado inmediatamente y la operación podría, o no, cancelarse realmente. En el controlador de eventos del evento nombreDeMétodoCompleted, el objeto nombreDeMétodoCompletedEventArgs contiene un campo Cancelled, que los clientes pueden utilizar para determinar si se ha producido la cancelación.

Aténgase a la semántica de cancelación descrita en Procedimientos recomendados para implementar el modelo asincrónico basado en eventos.

Admisión opcional de la propiedad IsBusy

Si una clase no admite varias invocaciones simultáneas, considere la posibilidad de exponer una propiedad IsBusy. Esto permite a los desarrolladores determinar si se está ejecutando un método nombreDeMétodoAsync sin detectar una excepción del método nombreDeMétodoAsync.

Aténgase a la semántica de IsBusy descrita en Procedimientos recomendados para implementar el modelo asincrónico basado en eventos.

Compatibilidad opcional con la información de progreso

Con frecuencia se desea que una operación asincrónica informe sobre el progreso de su actividad. El modelo asincrónico basado en eventos proporciona una pauta para ello.

  • Defina opcionalmente el evento que quiere que desencadene la operación asincrónica y que se invoque en el subproceso adecuado. El objeto ProgressChangedEventArgs lleva un indicador de progreso con un valor entero que se espera que esté entre 0 y 100.

  • Denomine este evento como se indica a continuación:

    • ProgressChanged si la clase tiene varias operaciones asincrónicas (o si se espera que crezca y llegue a incluir varias operaciones asincrónicas en versiones futuras);

    • nombreDeMétodoProgressChanged si la clase tiene una operación asincrónica única.

    Esta opción de denominación es paralela a la realizada para el método de cancelación, tal y como se describe en la sección Admisión opcional de la cancelación.

Este evento debería utilizar la firma del delegado ProgressChangedEventHandler y la clase ProgressChangedEventArgs. Como alternativa, si se puede proporcionar otro indicador de progreso mas específico de dominio (por ejemplo, bytes leídos y bytes totales para una operación de descarga), debería definir una clase derivada de ProgressChangedEventArgs.

Observe que sólo hay un evento ProgressChanged o nombreDeMétodoProgressChanged para la clase, independientemente del número de métodos asincrónicos que admita. Se espera que los clientes utilicen el objeto userState que se pasa a los métodos nombreDeMétodoAsync para distinguir entre las actualizaciones de progreso que se producen en varias operaciones simultáneas.

Puede haber situaciones en las que varias operaciones admiten el progreso y cada una devuelve un indicador diferente del mismo. En este caso, un evento ProgressChanged único no es adecuado, por lo que puede resultar conveniente admitir varios eventos ProgressChanged. Si éste es el caso, utilice un modelo de denominación de nombreDeMétodoProgressChanged para cada método nombreDeMétodoAsync.

Aténgase a la semántica de información de progreso descrita en Procedimientos recomendados para implementar el modelo asincrónico basado en eventos.

Compatibilidad opcional con la devolución de resultados incrementales

A veces, una operación asincrónica puede devolver resultados incrementales antes de finalizar. Hay varias opciones que se pueden utilizar para que admitan esta posibilidad. A continuación, se dan algunos ejemplos.

Clase de operación única

Si una clase sólo admite una operación asincrónica única y esa operación puede devolver resultados incrementales, entonces:

  • Amplíe el tipo ProgressChangedEventArgs para que lleve los datos del resultado incremental y defina un evento nombreDeMétodoProgressChanged con estos datos extendidos.

  • Provoque este evento nombreDeMétodoProgressChanged cuando haya un resultado incremental sobre el que informar.

Esta solución es específica de las clases de operaciones asincrónicas únicas, porque no hay ningún problema en que un mismo evento devuelva resultados incrementales en "todas las operaciones", como sucede con el evento nombreDeMétodoProgressChanged.

Clase de varias operaciones con resultados incrementales homogéneos

En este caso, la clase admite varios métodos asincrónicos, cada uno de ellos capaz de devolver resultados incrementales, y estos resultados incrementales tienen todos el mismo tipo de datos.

Siga el modelo descrito más arriba para las clases de operación única, ya que la estructura EventArgs es la misma para todos los resultados incrementales. Defina un evento ProgressChanged en lugar de un evento nombreDeMétodoProgressChanged, ya que éste es válido para varios métodos asincrónicos.

Clase de varias operaciones con resultados incrementales heterogéneos

Si la clase admite varios métodos asincrónicos y cada uno de ellos devuelve un tipo diferente de datos:

  • Separe el informe sobre el resultado incremental del informe sobre el progreso.

  • Defina un evento nombreDeMétodoProgressChanged independiente con el tipo EventArgs adecuado para que cada método asincrónico pueda controlar los datos del resultado incremental de ese método.

Invoque el controlador de dicho evento en el subproceso adecuado, tal y como se describe en Procedimientos recomendados para implementar el modelo asincrónico basado en eventos.

Uso de los parámetros Out y Ref en métodos

Aunque en general no se recomienda el uso de out y ref en .NET Framework, he aquí las reglas que se deben seguir cuando están presentes:

Dado un método sincrónico nombreDeMétodo:

  • Los parámetros out del método nombreDeMétodo no deberían formar parte de nombreDeMétodoAsync. En su lugar, deberían formar parte de nombreDeMétodoCompletedEventArgs con el mismo nombre que su parámetro equivalente en nombreDeMétodo (a menos que haya un nombre más adecuado).

  • Los parámetros ref del método nombreDeMétodo deberían aparecer como parte de nombreDeMétodoAsync, y también como parte de nombreDeMétodoCompletedEventArgs con el mismo nombre que su parámetro equivalente en nombreDeMétodo (a menos que haya un nombre más adecuado).

Por ejemplo, dado el código:

Public Function MethodName(ByVal arg1 As String, ByRef arg2 As String, ByRef arg3 As String) As Integer
public int MethodName(string arg1, ref string arg2, out string arg3);

El método asincrónico y la clase AsyncCompletedEventArgs tendrían el siguiente aspecto:

Public Sub MethodNameAsync(ByVal arg1 As String, ByVal arg2 As String)

Public Class MethodNameCompletedEventArgs
    Inherits System.ComponentModel.AsyncCompletedEventArgs
    Public ReadOnly Property Result() As Integer 
    End Property
    Public ReadOnly Property Arg2() As String 
    End Property
    Public ReadOnly Property Arg3() As String 
    End Property
End Class
public void MethodNameAsync(string arg1, string arg2);

public class MethodNameCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs
{
    public int Result { get; };
    public string Arg2 { get; };
    public string Arg3 { get; };
}

Vea también

Tareas

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

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

Cómo: Implementar un formulario que utiliza una operación en segundo plano

Conceptos

Decidir cuándo implementar el modelo asincrónico basado en eventos

Procedimientos recomendados para implementar el modelo asincrónico basado en eventos

Referencia

ProgressChangedEventArgs

AsyncCompletedEventArgs

Otros recursos

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