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

El modelo asincrónico basado en eventos constituye una manera eficaz de exponer el comportamiento asincrónico en clases, con semántica conocida de delegado y evento. Para implementar el modelo asincrónico basado en eventos, es necesario seguir algunos requisitos de comportamiento concretos. En las secciones siguientes se describen los requisitos e instrucciones que se deben tener en cuenta al implementar una clase que sigue el modelo asincrónico basado en eventos.

Para obtener información general, vea Implementar el modelo asincrónico basado en eventos.

A continuación, se muestra una lista con los procedimientos recomendados que se van a tratar en este tema:

  • Garantías de comportamiento obligatorias

  • Finalización

  • EventArgs y evento finalizado

  • Operaciones que se ejecutan simultáneamente

  • Acceso a los resultados

  • Información de progreso

  • Implementación de IsBusy

  • Cancelación

  • Errores y excepciones

  • Subprocesamiento y contextos

  • Instrucciones

Garantías de comportamiento obligatorias

Cuando se implementa el modelo asincrónico basado en eventos, debe proporcionarse una serie de garantías que aseguren que la clase se va a comportar adecuadamente y que los clientes de dicha clase pueden confiar en dicho comportamiento.

Finalización

Invoque el controlador de eventos MethodNameCompleted siempre que la finalización sea correcta, o cuando se produzca un error o una cancelación. Nunca debería ocurrir que una aplicación permaneciese inactiva sin llegar nunca a la finalización. Como excepción a esta regla, está el caso en que la propia operación asincrónica está diseñada para no finalizar nunca.

EventArgs y evento finalizado

Para cada método MethodNameAsync independiente, aplique los requisitos de diseño siguientes:

  • Defina un evento MethodNameCompleted en la misma clase que el método.

  • Defina una clase EventArgs y el delegado que la acompaña para el evento MethodNameCompleted que deriva de la clase AsyncCompletedEventArgs. El nombre de clase predeterminado debería adoptar la forma MethodNameCompletedEventArgs.

  • Asegúrese de que la clase EventArgs es específica de los valores devueltos por el método MethodName. Cuando utilice la clase EventArgs, nunca debería exigirles a los desarrolladores que conviertan el resultado.

    En el ejemplo de código siguiente se muestra, respectivamente, una implementación buena y otra mala de este requisito de diseño.

[C#]

// Good design
private void Form1_MethodNameCompleted(object sender, xxxCompletedEventArgs e) 
{ 
    DemoType result = e.Result;
}

// Bad design
private void Form1_MethodNameCompleted(object sender, MethodNameCompletedEventArgs e) 
{ 
    DemoType result = (DemoType)(e.Result);
}
  • No defina una clase EventArgs para devolver los métodos que devuelven void. En su lugar, use una instancia de la clase AsyncCompletedEventArgs.

  • Asegúrese de que se genere siempre el evento NombreMétodoCompleted. Este evento se debe generar cuando se complete correctamente, cuando se produzca un error o cuando se cancele. Nunca debería ocurrir que una aplicación permaneciese inactiva sin llegar nunca a la finalización.

  • Asegúrese de que se detecte cualquier excepción que se produzca en la operación asincrónica y se asigne la excepción detectada a la propiedad Error.

  • Si se produjo un error al completar la tarea, los resultados no deben ser accesibles. Cuando la propiedad Error no es null, asegúrese de que el acceso a cualquier propiedad de la estructura EventArgs genere una excepción. Utilice el método RaiseExceptionIfNecessary para realizar la comprobación anterior.

  • Modele un tiempo de espera como un error. Cuando se produzca un tiempo de espera, genere el evento NombreMétodoCompleted y asigne TimeoutException a la propiedad Error.

  • Si la clase admite varias invocaciones simultáneas, asegúrese de que el evento NombreMétodoCompleted contenga el objeto userSuppliedState apropiado.

  • Asegúrese de que el evento NombreMétodoCompleted se genere en el subproceso adecuado y en el momento oportuno del ciclo de vida de la aplicación. Para obtener más información, vea la sección Subprocesamiento y contextos.

Operaciones que se ejecutan simultáneamente

  • Si la clase admite varias invocaciones simultáneas, deje que el programador haga un seguimiento independiente de cada invocación definiendo la sobrecarga MethodNameAsync que toma un parámetro de estado con valor de objeto, o un identificador de tarea, denominado userSuppliedState. Dicho parámetro siempre debería ser el último parámetro en la firma del método MethodNameAsync.

  • Si la clase define la sobrecarga MethodNameAsync que toma un parámetro de estado con valor de objeto, o un identificador de tarea, asegúrese de realizar el seguimiento de la duración de la operación con ese identificador de tarea y de devolverlo al controlador de finalización. Hay clases de ayuda disponibles para asistir este proceso. Para obtener más información sobre la administración de operaciones simultáneas, vea Tutorial: Implementar un componente que admita el modelo asincrónico basado en eventos.

  • Si la clase define el método MethodNameAsync sin el parámetro de estado y no admite varias invocaciones simultáneas, asegúrese de que cualquier intento de invocar MethodNameAsync, antes de que haya finalizado la invocación previa de MethodNameAsync, produzca una excepción InvalidOperationException.

  • En términos generales, no produzca una excepción si se invoca varias veces el método MethodNameAsync sin el parámetro userSuppliedState, de manera que haya varias operaciones pendientes. Puede provocar una excepción cuando la clase no puede controlar explícitamente esa situación, pero se supone que los desarrolladores pueden controlar todas estas devoluciones de llamada imposibles de distinguir.

Acceso a los resultados

Información de progreso

  • Si es posible, permita que se genere información de progreso. Esto permitirá a los desarrolladores ofrecer una mejor experiencia al usuario de la aplicación cuando utilicen la clase en cuestión.

  • Si implementa un evento ProgressChanged/MethodNameProgressChanged, asegúrese de que no se ha producido ningún evento de este tipo para ninguna operación asincrónica específica una vez producido el evento MethodNameCompleted para dicha operación.

  • Si se rellena el objeto ProgressChangedEventArgs estándar, asegúrese de que ProgressPercentage siempre se puede interpretar como un porcentaje. El porcentaje no tiene que ser preciso, basta con que represente un porcentaje. Si la medida utilizada en el informe sobre el progreso no puede ser un porcentaje, derive una clase de la clase ProgressChangedEventArgs y marque ProgressPercentage como 0. Evite utilizar una medida para el informe que no sea un porcentaje.

  • Asegúrese de que el evento ProgressChanged se provoca en el subproceso adecuado y en el momento oportuno del ciclo de vida de la aplicación. Para obtener más información, vea la sección Subprocesamiento y contextos.

Implementación de IsBusy

  • No exponga una propiedad IsBusy si la clase admite varias invocaciones simultáneas. Por ejemplo, los servidores proxy del servicio Web XML no exponen una propiedad IsBusy porque admiten varias invocaciones simultáneas de métodos asincrónicos.

  • La propiedad IsBusy debería devolver true después de haber llamado al método MethodNameAsync y antes de que se haya provocado el evento MethodNameCompleted. Si no fuese así, debería devolver false. Los componentes BackgroundWorker y WebClient son ejemplos de clases que exponen una propiedad IsBusy.

Cancelación

  • Si es posible, permita la posibilidad de cancelación. Esto permitirá a los desarrolladores ofrecer una mejor experiencia al usuario de la aplicación cuando utilicen la clase en cuestión.

  • En caso de cancelación, establezca el marcador Cancelled en el objeto AsyncCompletedEventArgs.

  • Asegúrese de que cualquier intento de obtener acceso al resultado produce una excepción InvalidOperationException, que informa de que se ha cancelado la operación. Utilice el método AsyncCompletedEventArgs.RaiseExceptionIfNecessary para realizar la comprobación anterior.

  • Asegúrese de que las llamadas a un método de cancelación se realicen correctamente y no produzcan nunca una excepción. Como norma general, no se notifica a los clientes si una operación es realmente cancelable en cualquier momento, ni tampoco si una cancelación emitida con anterioridad ha tenido éxito. Sin embargo, siempre se notificará a la aplicación si la cancelación ha tenido éxito, ya que la aplicación participa en el estado de realización.

  • Provoque el evento MethodNameCompleted cuando se cancele la operación.

Errores y excepciones

  • Detecte cualquier excepción que se produzca en la operación asincrónica y establezca esa excepción como valor de la propiedad AsyncCompletedEventArgs.Error.

Subprocesamiento y contextos

Para que la clase funcione correctamente, es fundamental que los controladores de eventos del cliente se invoquen en el subproceso o contexto adecuado para dicho modelo de aplicación, incluidas las aplicaciones de Windows Forms y ASP.NET. Se proporcionan dos clases de ayuda importantes para garantizar que la clase asincrónica se comporta correctamente bajo cualquier modelo de aplicación: AsyncOperation y AsyncOperationManager.

AsyncOperationManager proporciona un método, CreateOperation, que devuelve un objeto AsyncOperation. El método MethodNameAsync llama a CreateOperation y la clase utiliza el objeto AsyncOperation devuelto para realizar el seguimiento del período de duración de la tarea asincrónica.

Para informar al cliente sobre el progreso, los resultados incrementales y la finalización, llame a los métodos Post y OperationCompleted en AsyncOperation. AsyncOperation es responsable de calcular las referencias de las llamadas a los controladores de eventos del cliente en el contexto o subproceso adecuados.

NotaNota

Puede no seguir estas reglas si quiere contradecir explícitamente las directivas del modelo de aplicación; pero, incluso así, podrá seguir beneficiándose de las otras ventajas de utilizar el modelo asincrónico basado en eventos.Por ejemplo, tal vez quiera que una clase que funciona en los formularios Windows Forms tenga subprocesamiento libre.Puede crear una clase con subprocesamiento libre, con tal de que los desarrolladores entiendan las restricciones que esto implica.Las aplicaciones de consola no sincronizan la ejecución de llamadas Post.Esto puede hacer que los eventos ProgressChanged se produzcan en un orden incorrecto.Si desea tener una ejecución serializada de llamadas Post, implemente e instale una clase System.Threading.SynchronizationContext.

Para obtener más información sobre cómo utilizar AsyncOperation y AsyncOperationManager para habilitar sus operaciones asincrónicas, vea Tutorial: Implementar un componente que admita el modelo asincrónico basado en eventos.

Instrucciones

  • Lo ideal es que cada invocación de método debería ser independiente de las otras. Debería evitar asociar las invocaciones con recursos compartidos. Si fuese necesario compartir los recursos entre las invocaciones, tendrá que utilizar un mecanismo de sincronización apropiado en la implementación.

  • No se recomiendan los diseños que exigen al cliente que implemente sincronización. Por ejemplo, si tiene un método asincrónico que recibe un objeto estático global como parámetro, varias invocaciones simultáneas de dicho método podrían dar como resultado interbloqueos o datos dañados.

  • Si implementa un método con la sobrecarga de varias invocaciones (userState en la firma), la clase tendrá que administrar una colección de estados de usuario, o identificadores de tarea, y las operaciones pendientes correspondientes. Esta colección se debería proteger con regiones lock, porque las distintas invocaciones agregan y quitan objetos userState en la colección.

  • Siempre que sea factible y conveniente, considere la posibilidad de reutilizar clases CompletedEventArgs. En este caso, los nombres asignados no son coherentes con el nombre de método, ya que un delegado dado y el tipo EventArgs no están asociados a un único método. Sin embargo, no es aceptable obligar a los desarrolladores a convertir el valor recuperado de una propiedad en EventArgs.

  • Si está creando una clase que deriva de Component, no implemente ni instale su propia clase SynchronizationContext. Los modelos de la aplicación, no los componentes, son los que controlan la clase SynchronizationContext que se utiliza.

  • Al utilizar multithreading de cualquier tipo, el usuario se expone potencialmente a errores muy serios y complejos. Antes de implementar cualquier solución que utilice multithreading, vea Procedimientos recomendados para el subprocesamiento administrado.

Vea también

Tareas

Cómo: Utilizar componentes que admitan el modelo asincrónico basado en eventos

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

Referencia

AsyncOperation

AsyncOperationManager

AsyncCompletedEventArgs

ProgressChangedEventArgs

BackgroundWorker

Conceptos

Implementar el modelo asincrónico basado en eventos

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

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

Otros recursos

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