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

Las aplicaciones que desempeñan muchas tareas simultáneamente, aunque siguen respondiendo a la interacción del usuario, a menudo exigen un diseño que utiliza varios subprocesos. El espacio de nombres System.Threading proporciona todas las herramientas necesarias para crear aplicaciones multiproceso de gran rendimiento, pero, para usar estas herramientas de forma eficaz, es necesario atesorar una gran experiencia en ingeniería de software multiproceso. Para aplicaciones multiproceso relativamente simples, el componente BackgroundWorker ofrece una solución sencilla. Para aplicaciones asincrónicas más sofisticadas, considere la opción de implementar una clase que se adhiera al modelo asincrónico basado en eventos.

El modelo asincrónico basado en eventos pone a su disposición las ventajas de las aplicaciones multiproceso al tiempo que oculta muchos de los problemas complejos inherentes al diseño multiproceso. El uso de una clase compatible con este modelo permite:

  • Realizar «en segundo plano» tareas que exigen mucho tiempo, como las descargas y las operaciones de base de datos, sin interrumpir la aplicación.

  • Ejecutar varias operaciones simultáneamente y recibir una notificación cuando finalice cada una de ellas.

  • Esperar a que los recursos estén disponibles sin detener la aplicación, es decir, sin que se quede "bloqueada".

  • Comunicarse con operaciones asincrónicas pendientes mediante el modelo conocido de eventos y delegados. Para obtener más información sobre el uso de controladores de eventos y delegados, vea Eventos.

Una clase que admita el modelo asincrónico basado en eventos tendrá uno o varios métodos denominados NombreDeMétodoAsync. Estos métodos pueden reflejar versiones sincrónicas, que desempeñan la misma operación en el subproceso actual. La clase también puede tener un evento NombreDeMétodoCompleted y un método NombreDeMétodoAsyncCancel (o simplemente CancelAsync).

PictureBox es un componente que admite el modelo asincrónico basado en eventos. Se puede descargar una imagen sincrónicamente mediante una llamada a su método Load, pero si la imagen es grande o si la conexión de red es lenta, la aplicación dejará de responder hasta que la operación finalice y la llamada a Load responda.

Si quiere que la aplicación siga ejecutándose mientras se carga la imagen, puede llamar al método LoadAsync y controlar el evento LoadCompleted, al igual que haría con cualquier otro evento. Cuando llame al método LoadAsync, la aplicación seguirá ejecutándose mientras se realiza la descarga en un subproceso aparte («en segundo plano»). Cuando la operación de carga de la imagen finalice, se llamará al controlador de eventos; este puede examinar el parámetro AsyncCompletedEventArgs para determinar si la descarga se completó correctamente.

El modelo asincrónico basado en eventos requiere que se pueda cancelar una operación asincrónica y que el control PictureBox admita este requisito con su método CancelAsync. La llamada a CancelAsync envía una solicitud para detener la carga pendiente y, cuando se cancela la operación, se genera el evento LoadCompleted.

Precaución

Es posible que la descarga finalice justo en el momento de realizar la solicitud CancelAsync y, por tanto, puede que Cancelled no refleje la solicitud de cancelación. Esto se denomina condición de carrera y es un problema habitual en la programación multiproceso. Para obtener más información sobre problemas en la programación multiproceso, vea Procedimientos recomendados para el subprocesamiento administrado.

Características del modelo asincrónico basado en eventos

El modelo asincrónico basado en eventos puede tener varias formas, en función de la complejidad de las operaciones admitidas por una clase en particular. Las clases más simples pueden tener un único método NombreDeMétodoAsync y su correspondiente evento NombreDeMétodoCompleted. Las clases más complejas pueden tener varios métodos NombreDeMétodoAsync, cada uno con su correspondiente evento NombreDeMétodoCompleted, así como versiones sincrónicas de estos métodos. Opcionalmente, las clases pueden admitir cancelación, informes de progreso y resultados incrementales para los métodos asincrónicos.

Un método asincrónico también puede admitir varias llamadas pendientes (varias invocaciones simultáneas), lo que permite que su código pueda llamarlo todas las veces que quiera sin que se hayan completado las otras operaciones pendientes. La gestión correcta de esta situación puede exigir a la aplicación realizar un seguimiento de la finalización de cada operación.

Ejemplos del modelo asincrónico basado en eventos

Los componentes SoundPlayer and PictureBox representan implementaciones simples del modelo asincrónico basado en eventos. Los componentes WebClient and BackgroundWorker representan implementaciones más complejas del modelo asincrónico basado en eventos.

El siguiente es un ejemplo de declaración de clase que se ajusta al modelo:

Public Class AsyncExample  
    ' Synchronous methods.  
    Public Function Method1(ByVal param As String) As Integer
    Public Sub Method2(ByVal param As Double)
  
    ' Asynchronous methods.  
    Overloads Public Sub Method1Async(ByVal param As String)
    Overloads Public Sub Method1Async(ByVal param As String, ByVal userState As Object)
    Public Event Method1Completed As Method1CompletedEventHandler  
  
    Overloads Public Sub Method2Async(ByVal param As Double)
    Overloads Public Sub Method2Async(ByVal param As Double, ByVal userState As Object)
    Public Event Method2Completed As Method2CompletedEventHandler  
  
    Public Sub CancelAsync(ByVal userState As Object)
  
    Public ReadOnly Property IsBusy () As Boolean  
  
    ' Class implementation not shown.  
End Class  
public class AsyncExample  
{  
    // Synchronous methods.  
    public int Method1(string param);  
    public void Method2(double param);  
  
    // Asynchronous methods.  
    public void Method1Async(string param);  
    public void Method1Async(string param, object userState);  
    public event Method1CompletedEventHandler Method1Completed;  
  
    public void Method2Async(double param);  
    public void Method2Async(double param, object userState);  
    public event Method2CompletedEventHandler Method2Completed;  
  
    public void CancelAsync(object userState);  
  
    public bool IsBusy { get; }  
  
    // Class implementation not shown.  
}  

La clase AsyncExample ficticia tiene dos métodos, los cuales admiten invocaciones de tipo sincrónico y asincrónico. Las sobrecargas sincrónicas se comportan como cualquier llamada al método y ejecutan la operación en el subproceso que realiza la llamada; si la operación lleva mucho tiempo, puede producirse un retraso notable antes de que la llamada responda. Las sobrecargas asincrónicas empezarán la operación en otro subproceso y luego volverán de inmediato, lo que permite continuar el subproceso de llamada mientras la operación se ejecuta «en segundo plano».

Sobrecargas de método asincrónico

Hay dos posibles sobrecargas para las operaciones asincrónicas: invocación única e invocación múltiple. Las dos formas se distinguen por sus signaturas de método: la forma de invocación múltiple tiene un parámetro adicional llamado userState. Esta forma permite al código llamar varias veces a Method1Async(string param, object userState) sin tener que esperar a que finalicen otras operaciones asincrónicas pendientes. Por otra parte, si se intenta llamar a Method1Async(string param) antes de que haya finalizado una invocación anterior, el método genera una InvalidOperationException.

El parámetro userState para sobrecargas de invocación múltiple permite distinguir entre operaciones asincrónicas. Usted proporciona un valor único (por ejemplo, un GUID o código hash) para cada llamada a Method1Async(string param, object userState), y cuando la operación finalice, el controlador de eventos podrá determinar qué instancia de la operación fue la que generó el evento de finalización.

Seguimiento de operaciones pendientes

Si usa sobrecargas de invocación múltiple, el código necesitará realizar un seguimiento de los objetos userState (id. de tareas) de las tareas pendientes. En cada llamada a Method1Async(string param, object userState), normalmente hay que generar un objeto userState nuevo y único y agregarlo a una colección. Cuando la tarea correspondiente a este objeto userState genera el evento de finalización, su implementación del método de finalización examinará AsyncCompletedEventArgs.UserState y lo quitará de su colección. Usado de este modo, el parámetro userState adopta el rol de id. de tarea.

Nota

Asegúrese de proporcionar un valor único para userState en las llamadas a sobrecargas de invocación múltiple. Los id. de tarea que no sean únicos provocarán que la clase asincrónica genere una ArgumentException.

Cancelación de operaciones pendientes

Es importante ser capaz cancelar operaciones asincrónicas en cualquier momento antes de la finalización. Las clases que implementan el modelo asincrónico basado en eventos tendrán un método CancelAsync (si solo hay un método asincrónico) o un método NombreDeMétodoAsyncCancel (si hay varios métodos asincrónicos).

Los métodos que permiten varias invocaciones toman un parámetro userState que puede usarse para seguir la duración de cada tarea. CancelAsync toma un parámetro userState que permite cancelar tareas pendientes concretas.

Los métodos que solo admiten una única operación pendiente a la vez, como Method1Async(string param), no son cancelables.

Recibir actualizaciones de progreso y resultados incrementales

Una clase que se adhiera al modelo asincrónico basado en eventos puede, como opción, proporcionar un evento para seguir el progreso y los resultados incrementales. Por lo general, se denominará ProgressChanged o NombreDeMétodoProgressChanged, y su controlador de eventos correspondiente tomará un parámetro ProgressChangedEventArgs.

El controlador de eventos del evento ProgressChanged puede examinar la propiedad ProgressChangedEventArgs.ProgressPercentage para determinar el progreso de una tarea asincrónica expresado mediante porcentaje. Esta propiedad estará comprendida entre 0 y 100 y se puede usar para actualizar la propiedad Value de un ProgressBar. Si hay varias operaciones asincrónicas pendientes, puede usar la propiedad ProgressChangedEventArgs.UserState para distinguir cuál de ellas está informando del progreso.

Algunas clases pueden informar de resultados incrementales durante el desarrollo de operaciones asincrónicas. Estos resultados se almacenarán en una clase que deriva de ProgressChangedEventArgs y aparecerán como propiedades en la clase derivada. El acceso a estos resultados en el controlador de eventos del evento ProgressChanged se realiza del mismo modo que el acceso a la propiedad ProgressPercentage. Si hay varias operaciones asincrónicas pendientes, puede usar la propiedad UserState para distinguir cuál de ellas está informando de resultados incrementales.

Vea también