Compartir a través de


Implementación del patrón asincrónico basado en eventos

Si va a escribir una clase con algunas operaciones que pueden provocar retrasos notables, considere la posibilidad de proporcionar funcionalidad asincrónica mediante la implementación del patrón asincrónico basado en eventos.

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

Para obtener un ejemplo que implemente el patrón asincrónico basado en eventos, vea Cómo: Implementar un componente que admita el patrón asincrónico basado en eventos.

Para operaciones asincrónicas sencillas, puede encontrar el BackgroundWorker componente adecuado. Para obtener más información sobre BackgroundWorker, vea Cómo: Ejecutar una operación en segundo plano.

En la lista siguiente se describen las características del patrón asincrónico basado en eventos descrito en este tema.

  • Oportunidades para implementar el patrón asincrónico basado en eventos

  • Nomenclatura de métodos asincrónicos

  • Admite la cancelación de forma opcional

  • Opcionalmente, admiten la propiedad IsBusy

  • Ofrezca opcionalmente soporte para la presentación de informes de progreso.

  • Opcionalmente, proporcionan compatibilidad para devolver resultados incrementales

  • Manejo de los parámetros out y ref en métodos

Oportunidades para implementar el patrón asincrónico basado en eventos

Considere la posibilidad de implementar el patrón asincrónico basado en eventos cuando:

  • Los clientes de la clase no necesitan que los objetos WaitHandle y IAsyncResult estén disponibles para operaciones asincrónicas, lo que significa que el sondeo y WaitAll o WaitAny deberá crearlos el cliente.

  • Desea que el cliente administre las operaciones asincrónicas con el modelo delegado o de evento familiar.

Cualquier operación es candidata para una implementación asincrónica, pero se deben considerar especialmente aquellas que se espera que incurran en latencias largas. Son especialmente adecuadas las operaciones en las cuales los clientes llaman a un método y reciben una notificación de su conclusión, sin necesidad de ninguna otra intervención. También son adecuadas las operaciones que se ejecutan continuamente, notificando periódicamente a los clientes el progreso, los resultados incrementales o los cambios de estado.

Para obtener más información sobre cómo decidir cuándo admitir el patrón asincrónico basado en eventos, vea Decidir cuándo implementar el patrón asincrónico basado en eventos.

Nomenclatura de métodos asincrónicos

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

Defina un método AsincrónicoMethodName que:

  • Devuelve void.

  • Toma los mismos parámetros que el método MethodName .

  • Acepta varias invocaciones.

Opcionalmente, defina una sobrecarga MethodNameAsync, idéntica a MethodNameAsync, pero con un parámetro adicional de tipo objeto denominado userState. Haga esto si está preparado para administrar varias invocaciones simultáneas del método, en cuyo caso el userState valor se devolverá a todos los controladores de eventos para distinguir las invocaciones del método. También puede optar por hacerlo simplemente como un lugar para almacenar el estado de usuario para su recuperación posterior.

Para cada firma de método independiente MethodNameAsync:

  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. Es probable que se definan 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 MethodNameCompletedEventArgs expone sus miembros como propiedades de solo lectura y no campos, ya que los campos impiden el enlace de datos.

    • No defina ninguna AsyncCompletedEventArgsclase derivada de métodos que no generen resultados. Simplemente se usa una instancia de AsyncCompletedEventArgs.

      Nota:

      Es perfectamente aceptable, cuando sea posible y apropiado, para reutilizar el delegado y los tipos AsyncCompletedEventArgs. En este caso, la nomenclatura no será tan coherente con el nombre del método, ya que un delegado determinado y AsyncCompletedEventArgs no estará vinculado a un único método.

Admite la cancelación de forma opcional

Si la clase admitirá la cancelación de operaciones asincrónicas, la cancelación debe exponerse al cliente como se describe a continuación. Hay dos puntos de decisión que deben alcanzarse antes de definir el soporte técnico de cancelación:

  • ¿Su clase, incluidas las futuras adiciones previstas, tiene solo una operación asincrónica que admita la cancelación?
  • ¿Pueden las operaciones asincrónicas que permiten la cancelación soportar varias operaciones pendientes? Es decir, ¿el método MethodNameAsync toma un userState parámetro y permite varias invocaciones antes de esperar a que finalice alguna?

Use las respuestas a estas dos preguntas de la tabla siguiente para determinar cuál debe ser la firma del método de cancelación.

Visual Basic

Se admiten varias operaciones simultáneas Solo una operación a la 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#

Se admiten varias operaciones simultáneas Solo una operación a la 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 CancelAsync(object userState) método, los clientes deben tener cuidado al elegir sus valores de estado para que sean capaces de distinguir entre todos los métodos asincrónicos invocados en el objeto y no solo entre todas las invocaciones de un único método asincrónico.

La decisión de asignar un nombre a la versión de operación asincrónica única MethodNameAsyncCancel se basa en poder detectar más fácilmente el método 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 haya operaciones asincrónicas adicionales agregadas en versiones posteriores, es mejor definir CancelAsync.

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

Normalmente, estos métodos volverán de inmediato y la operación puede cancelarse, o bien puede no hacerlo en realidad. En el controlador de eventos para el evento MethodNameCompleted , el objeto MethodNameCompletedEventArgs contiene un Cancelled campo, que los clientes pueden usar para determinar si se produjo la cancelación.

Cumpla la semántica de cancelación descrita en Procedimientos recomendados para implementar el patrón asincrónico basado en eventos.

Opcionalmente, admiten la propiedad IsBusy

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

Cumpla la IsBusy semántica descrita en Procedimientos recomendados para implementar el patrón asincrónico basado en eventos.

Ofrezca opcionalmente soporte para la presentación de informes de progreso.

Con frecuencia, es deseable que una operación asincrónica notifique el progreso durante su operación. El patrón asincrónico basado en eventos proporciona una guía para hacerlo.

  • Opcionalmente, defina un evento que genere la operación asincrónica y que se invoque en el subproceso adecuado. El ProgressChangedEventArgs objeto lleva un indicador de progreso con valores enteros que se espera que esté comprendido entre 0 y 100.

  • Asigne a este evento el nombre siguiente:

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

    • MethodNameProgressChanged si la clase tiene una sola operación asincrónica.

    Esta elección de nomenclatura es paralela a la que se realizó para el método de cancelación descrito en la sección Admitir opcionalmente la cancelación.

Este evento debe usar la ProgressChangedEventHandler firma del delegado y la ProgressChangedEventArgs clase . Como alternativa, si se puede proporcionar un indicador de progreso más específico del dominio (por ejemplo, bytes leídos y bytes totales para una operación de descarga), debe definir una clase derivada de ProgressChangedEventArgs.

Tenga en cuenta que para la clase solo hay un evento ProgressChangedMethodNameProgressChanged, independientemente del número de métodos asíncronos que admita. Se espera que los clientes utilicen el objeto userState que se pasa a los métodos MethodNameAsync para distinguir entre las actualizaciones de progreso en varias operaciones concurrentes.

Puede haber situaciones en las que varias operaciones admitan el progreso y cada una devuelva un indicador para el progreso diferente. En este caso, un solo evento ProgressChanged no es adecuado, pudiendo considerar la posibilidad de admitir varios eventos ProgressChanged. En este caso, use un patrón de nomenclatura de MethodNameProgressChanged para cada método MethodNameAsync.

Cumpla la semántica de notificación sobre el progreso descrita en Procedimientos recomendados para implementar el modelo asincrónico basado en eventos.

Opcionalmente, proporcionan compatibilidad para devolver resultados incrementales

A veces, una operación asincrónica puede devolver resultados incrementales antes de la finalización. Hay una serie de opciones que se pueden usar para admitir este escenario. A continuación se muestran algunos ejemplos.

Clase de operación única

Si la clase solo admite una sola operación asincrónica y esa operación puede devolver resultados incrementales, a continuación:

  • Extienda el ProgressChangedEventArgs tipo para llevar los datos de resultados incrementales y defina un evento MethodNameProgressChanged con estos datos extendidos.

  • Genere este evento MethodNameProgressChanged cuando haya un resultado incremental para informar.

Esta solución se aplica específicamente a una clase de operación asíncrona única porque no hay ningún problema con que el mismo evento ocurra para devolver resultados incrementales en "todas las operaciones", como hace el evento MethodNameProgressChanged.

Clase de varias operaciones con resultados incrementales homogéneos

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

Siga el modelo descrito anteriormente para las clases de operación única, ya que la misma EventArgs estructura funcionará para todos los resultados incrementales. Defina un ProgressChanged evento en lugar de un evento MethodNameProgressChanged , ya que se aplica a varios métodos asincrónicos.

Clase de varias operaciones con resultados incrementales heterogéneos

Si la clase admite varios métodos asincrónicos, cada uno de los cuales devuelve un tipo diferente de datos, debe:

  • Separe los informes de resultados incrementales de los informes de progreso.

  • Defina un evento MethodNameProgressChanged independiente con el adecuado EventArgs para cada método asincrónico para controlar los datos de resultados incrementales del método.

Invoque ese controlador de eventos en el subproceso adecuado, tal como se describe en Procedimientos recomendados para implementar el patrón asincrónico basado en eventos.

Manejo de los parámetros out y ref en métodos

Aunque el uso de out y ref es, en general, no se recomienda en .NET, estas son las reglas que se deben seguir cuando están presentes:

Dado un método sincrónico MethodName:

  • out Los parámetros de MethodName no deben formar parte de MethodNameAsync. En su lugar, deben formar parte de MethodNameCompletedEventArgs con el mismo nombre que su parámetro equivalente en MethodName (a menos que haya un nombre más adecuado).

  • ref Los parámetros de MethodName deben aparecer como parte de MethodNameAsync y como parte de MethodNameCompletedEventArgs con el mismo nombre que su parámetro equivalente en MethodName (a menos que haya un nombre más adecuado).

Por ejemplo, dado:

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 su AsyncCompletedEventArgs clase tendría este 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; };
}

Consulte también