Procedimientos recomendados para implementar el modelo asincrónico basado en eventos
El modelo asincrónico basado en eventos proporciona un método eficaz de exponer el comportamiento asincrónico en clases, con una semántica de delegados y eventos ya conocida. Para implementar el modelo asincrónico basado en eventos, es necesario cumplir algunos requisitos de comportamiento específicos. En las secciones siguientes se describen los requisitos y las instrucciones que se deben tener en cuenta a la hora de implementar una clase que responda a este modelo.
Para información general, consulte Implementación del patrón asincrónico basado en eventos.
Garantías de comportamiento necesarias
Si implementa el modelo asincrónico basado en eventos, debe ofrecer una serie de garantías que aseguren un comportamiento apropiado de su clase en el que puedan confiar sus clientes.
Completion
Invoque siempre al controlador de eventos NombreDeMétodoCompleted cuando se produzca un error, una cancelación o una finalización correcta. Las aplicaciones jamás deben encontrarse en una situación en la que permanezcan inactivas sin que se produzca una finalización. Una excepción a esta regla es que la misma operación asincrónica esté diseñada para no finalizar nunca.
Evento Completed y EventArgs
En cada método NombreDeMétodoAsync, aplique los requisitos de diseño siguientes:
Defina un evento NombreDeMétodoCompleted en la misma clase que el método.
Defina una clase EventArgs y un delegado adjunto para el evento NombreDeMétodoCompleted que deriva de la clase AsyncCompletedEventArgs. El nombre de clase predeterminado debe presentar el formato MethodNameCompletedEventArgs.
Asegúrese de que la clase EventArgs sea específica de los valores devueltos del método MethodName. Cuando use la clase EventArgs, nunca les pida a los desarrolladores que conviertan el resultado.
En el ejemplo de código siguiente se muestra una implementación correcta e incorrecta, respectivamente, de este requisito de diseño.
// 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 métodos que devuelvan
void
. En su lugar, use una instancia de la clase AsyncCompletedEventArgs.Asegúrese de que siempre genera el evento MethodNameCompleted. Este evento debe generarse cuando se produce una finalización correcta, un error o una cancelación. Las aplicaciones jamás deben encontrarse en una situación en la que permanezcan inactivas sin que se produzca una finalización.
Asegúrese de que captura todas las excepciones que se producen en la operación asincrónica y de que asigna la excepción capturada a la propiedad Error.
Si se produjo un error al finalizar la tarea, los resultados no deben ser accesibles. Si la propiedad Error no es
null
, asegúrese de que el acceso a cualquier propiedad de la estructura EventArgs genera una excepción. Use el método RaiseExceptionIfNecessary para realizar esta verificación.Cree un modelo de error para los agotamientos del tiempo de espera. Cuando se agote el tiempo de espera, genere el evento MethodNameCompleted y asigne TimeoutException a la propiedad Error.
Si su clase admite varias invocaciones simultáneas, asegúrese de que el evento MethodNameCompleted contiene el objeto
userSuppliedState
apropiado.Asegúrese de que el evento MethodNameCompleted se genera en el subproceso apropiado y en el momento adecuado del ciclo de vida de la aplicación. Para obtener más información, vea la sección Subprocesos y contextos.
Ejecución simultánea de operaciones
Si su clase admite varias invocaciones simultáneas, habilite al desarrollador para que realice un seguimiento por separado de cada invocación; para ello, defina la sobrecarga MethodNameAsync que toma un parámetro de estado con valor de objeto o identificador de tarea, denominado
userSuppliedState
. Este parámetro siempre debe ser el último de la signatura del método MethodNameAsync.Si su clase define la sobrecarga MethodNameAsync que toma un parámetro de estado con valor de objeto o identificador de tarea, asegúrese de realizar un seguimiento de la duración de la operación con ese identificador de tarea y de devolverlo al controlador de finalización. Hay clases del asistente que le servirán de ayuda. Para más información sobre la administración de simultaneidad, vea Tutorial: Implementación de un componente que admita el modelo asincrónico basado en eventos.
Si su 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 la invocación previa a MethodNameAsync se haya completado genere InvalidOperationException.
Por lo general, no genere una excepción si se invoca varias veces al método MethodNameAsync sin el parámetro
userSuppliedState
de modo que haya varias operaciones pendientes. Puede generar una excepción si la clase no puede controlar esa situación de forma explícita, pero piense que los desarrolladores pueden gestionar varias devoluciones de llamadas indistinguibles.
Acceso a resultados
Si se produjo un error durante la ejecución de la operación asincrónica, los resultados no pueden ser accesibles. Asegúrese de que el acceso a cualquier propiedad de AsyncCompletedEventArgs cuando Error no es
null
genera la excepción a la que hace referencia Error. La clase AsyncCompletedEventArgs proporciona el método RaiseExceptionIfNecessary para este fin.Asegúrese de que cualquier intento de acceder al resultado genere una InvalidOperationException en la que se declare que la operación se canceló. Use el método AsyncCompletedEventArgs.RaiseExceptionIfNecessary para realizar esta verificación.
Informes de progreso
Si es posible, admita informes de progreso. Esto permite a los desarrolladores ofrecer una mejor experiencia a los usuarios de la aplicación cuando utilicen su clase.
Si implementa un evento ProgressChanged o MethodNameProgressChanged, asegúrese de que no se han generado esos eventos para una operación asincrónica concreta después de que se haya generado el evento MethodNameCompleted de esa operación.
Si se rellena la clase ProgressChangedEventArgs estándar, asegúrese de que ProgressPercentage siempre se pueda interpretar como un porcentaje. No es necesario que el porcentaje sea exacto, pero debe representarse uno. Si su métrica de informes de progreso debe ser distinta a un porcentaje, derive una clase a partir de la clase ProgressChangedEventArgs y deje ProgressPercentage en 0. Evite usar una métrica de informes que no sea un porcentaje.
Asegúrese de que el evento
ProgressChanged
se genera en el subproceso apropiado y en el momento adecuado del ciclo de vida de la aplicación. Para obtener más información, vea la sección Subprocesos y contextos.
Implementación de IsBusy
No exponga una propiedad
IsBusy
si su clase admite varias invocaciones simultáneas. Por ejemplo, los proxy del servicio XML Web no exponen una propiedadIsBusy
porque admiten varias invocaciones simultáneas de métodos asincrónicos.La propiedad
IsBusy
debe devolvertrue
después de que se haya llamado al método MethodNameAsync y antes de que se haya generado el evento MethodNameCompleted. De lo contrario, debe devolverfalse
. Los componentes BackgroundWorker y WebClient son ejemplos de clases que exponen una propiedadIsBusy
.
Cancelación
Si es posible, admita cancelación. Esto permite a los desarrolladores ofrecer una mejor experiencia a los usuarios de la aplicación cuando utilicen su clase.
En caso de cancelación, establezca la marca Cancelled en el objeto AsyncCompletedEventArgs.
Asegúrese de que cualquier intento de acceder al resultado genere una InvalidOperationException en la que se declare que la operación se canceló. Use el método AsyncCompletedEventArgs.RaiseExceptionIfNecessary para realizar esta verificación.
Asegúrese de que las llamadas a un método de cancelación siempre se devuelvan correctamente y nunca generen una excepción. Por lo general, los clientes no son informados de si una operación es realmente cancelable en un momento determinado, ni tampoco de si una cancelación emitida previamente se ha realizado de forma correcta. No obstante, la aplicación siempre recibirá notificación cuando la cancelación tenga éxito, ya que la aplicación participa en el estado de finalización.
Genere el evento MethodNameCompleted cuando la operación se cancele.
Errores y excepciones
- Capture todas las excepciones que se produzcan en la operación asincrónica y establezca el valor de la propiedad AsyncCompletedEventArgs.Error en esa excepción.
Subprocesos y contextos
Para que la clase funcione correctamente, es vital que los controladores de eventos del cliente se invoquen en el subproceso o contexto apropiados para el modelo de aplicación concreto, incluidas aplicaciones de ASP.NET y de Windows Forms. Para garantizar que su clase asincrónica se comporta correctamente en cualquier modelo de aplicación, se proporcionan dos importantes clases del asistente: AsyncOperation y AsyncOperationManager.
AsyncOperationManager proporciona un método, CreateOperation, que devuelve una AsyncOperation. El método MethodNameAsync llama a CreateOperation y su clase usa la clase AsyncOperation devuelta para realizar un seguimiento de la duración de la tarea asincrónica.
Para informar al cliente del 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 llamadas a los controladores de eventos del cliente para el subproceso o contexto apropiado.
Nota
Puede sortear estas reglas si desea contravenir la directiva del modelo de aplicación, pero al mismo tiempo seguir beneficiándose de las otras ventajas de usar el modelo asincrónico basado en eventos. Por ejemplo, es posible que desee que una clase que opera en Windows Forms sea de subproceso libre. Puede crear una clase de subproceso libre si los desarrolladores entienden las restricciones implícitas. Las aplicaciones de consola no sincronizan la ejecución de llamadas Post. Esto puede provocar que se generen eventos ProgressChanged
fuera de lugar. Si desea serializar la ejecución de llamadas Post, implemente e instale una clase System.Threading.SynchronizationContext.
Para más información sobre el uso de AsyncOperation y AsyncOperationManager para habilitar las operaciones asincrónicas, vea Tutorial: Implementación de un componente que admita el modelo asincrónico basado en eventos.
Directrices
Lo ideal es que las invocaciones de métodos sean independiente entre sí. Debe evitarse el acoplamiento de invocaciones con recursos compartidos. Si los recursos se van a compartir entre invocaciones, es necesario proporcionar un mecanismo de sincronización adecuado en la implementación.
No se recomienda usar diseños que exijan al cliente implementar sincronización. Por ejemplo, si tiene un método asincrónico que recibe un objeto estático global como parámetro y se producen varias invocaciones simultáneas de ese método, se podrían dañar los datos o producir interbloqueos.
Si implementa un método con la sobrecarga de varias invocaciones (
userState
en la signatura), su clase necesitará administrar una colección de estados de usuario o identificadores de tarea, así como sus correspondientes operaciones pendientes. Esta colección debe protegerse con regioneslock
, ya que las diversas invocaciones agregan y quitan objetosuserState
de la colección.Considere la opción de volver a usar clases
CompletedEventArgs
donde sea posible y apropiado. En este caso, la nomenclatura no es coherente 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, jamás se puede forzar a los desarrolladores a que conviertan el valor recuperado de una propiedad en la clase EventArgs.Si va a crear una clase que deriva de Component, no implemente ni instale su propia clase SynchronizationContext. Los modelos de aplicación, y no los componentes, controlan el SynchronizationContext utilizado.
Si usa multithreading de cualquier tipo, corre el riesgo de que se produzcan errores graves y complejos. Antes de implementar cualquier solución que utilice el subprocesamiento múltiple, consulte Procedimientos recomendados para el subprocesamiento administrado.
Consulte también
- AsyncOperation
- AsyncOperationManager
- AsyncCompletedEventArgs
- ProgressChangedEventArgs
- BackgroundWorker
- Implementación del modelo asincrónico basado en eventos
- Modelo asincrónico basado en eventos (EAP)
- Decisión de cuándo implementar el modelo asincrónico basado en eventos
- Procedimientos recomendados para implementar el modelo asincrónico basado en eventos
- Cómo: Uso de componentes que admitan el modelo asincrónico basado en eventos
- Cómo: Implementación de un componente que admita el modelo asincrónico basado en eventos