Sei sulla pagina 1di 80

Modelos para la

Programacin

Asincrnica .NET
Modelos para la Programacin Asincrnica en .NET

Indice del Manual

1. Modelo asincrnico basado en tareas (TAP)


1.1. Implementar el modelo asincrnico basado en tareas
1.2. Utilizar el modelo asincrnico basado en tareas
1.3. Interoperabilidad con otros tipos y patrones asincrnicos
2. Patrn asincrnico basado en eventos (EAP)
2.1. Programacin multiproceso con el modelo asincrnico basado en eventos
2.1.1.Informacin general sobre el modelo asincrnico basado en eventos
2.1.2.Implementar el modelo asincrnico basado en eventos
2.1.3.Procedimientos recomendados para implementar el modelo asincrnico basado
en eventos
2.1.4.Decidir cundo implementar el modelo asincrnico basado en eventos
2.1.5.Tutorial: Implementar un componente que admita el modelo asincrnico basado
en eventos
2.1.5.1. Cmo: Implementar un componente que admita el modelo asincrnico
basado en eventos
2.1.5.2. Cmo: Implementar un cliente en un modelo asincrnico basado en
eventos
2.1.6.Cmo: Utilizar componentes que admitan el modelo asincrnico basado en
eventos
3. Modelo de programacin asincrnica (APM)
3.1. Llamar a mtodos asincrnicos mediante IAsyncResult
3.1.1.Bloquear la ejecucin de una aplicacin mediante AsyncWaitHandle
3.1.2.Bloquear la ejecucin de una aplicacin al finalizar una operacin asincrnica
3.1.3.Sondear el estado de una operacin asincrnica
3.1.4.Utilizar un delegado AsyncCallback para finalizar una operacin asincrnica
3.1.4.1. Utilizar un delegado AsyncCallback y un objeto State
3.2. Programacin asincrnica mediante delegados
3.2.1.Llamar a mtodos sincrnicos de forma asincrnica
3.2.2.Ejemplo de programacin de delegados asincrnicos

MCT: Luis Dueas Pag 2 de 80


Modelos para la Programacin Asincrnica en .NET

Modelos para la Programacin Asincrnica en .NET


.NET Framework ofrece tres patrones para realizar operaciones asincrnicas:
El modelo de programacin asincrnica (APM) (tambin denominado patrn IAsyncResult), donde las
operaciones asincrnicas requieren los mtodos Begin y End (por ejemplo, BeginWrite y EndWrite para las
operaciones de escritura asincrnicas). No se recomienda este patrn en desarrollos nuevos.
El patrn asincrnico basado en eventos (EAP), que requiere un mtodo que tiene el sufijo Async y uno o ms
eventos, tipos delegado de controlador de eventos y tipos derivados de EventArg. EAP apareci por primera
vez en .NET Framework 2.0. No es recomendable en desarrollos nuevos.
Modelo asincrnico basado en tareas (TAP), que usa un solo mtodo para representar el inicio y la finalizacin
de una operacin asincrnica. TAP apareci por primera vez en .NET Framework 4 y es el enfoque
recomendado para la programacin asincrnica de .NET Framework.

Comparar patrones
Para una comparacin rpida de cmo modelan los tres patrones las operaciones asincrnicas, considere un
mtodo Read que lee cierta cantidad de datos en un bfer proporcionado empezando desde un desplazamiento
especificado:
public class MyClass
{
public int Read(byte [] buffer, int offset, int count);
}

El equivalente de APM de este mtodo expondra los mtodos BeginRead y EndRead:


public class MyClass
{
public IAsyncResult BeginRead(byte [] buffer, int offset, int count,
AsyncCallback callback, object state);
public int EndRead(IAsyncResult asyncResult);
}

El equivalente de EAP expondra el siguiente conjunto de tipos y de miembros:


public class MyClass
{
public void ReadAsync(byte [] buffer, int offset, int count);
public event ReadCompletedEventHandler ReadCompleted;
}

El equivalente de TAP expondra el siguiente mtodo ReadAsync:


public class MyClass
{
public Task<int> ReadAsync(byte [] buffer, int offset, int count);
}

Para una visin completa de TAP, APM y EAP, consulte los vnculos proporcionados en la siguiente seccin.

Temas relacionados

Ttulo Descripcin

Modelo de programacin Describe el modelo heredado que utiliza la interfaz IAsyncResult para proporcionar un
asincrnica (APM) comportamiento asincrnico. Este modelo ya no se recomienda para nuevos
desarrollos.

Patrn asincrnico basado Describe el modelo heredado basado en eventos para proporcionar un comportamiento
en eventos (EAP) asincrnico. Este modelo ya no se recomienda para nuevos desarrollos.

Modelo asincrnico Describe el nuevo patrn asincrnico basado en el espacio de nombres System.
basado en tareas (TAP) Threading.Tasks. Este modelo es el enfoque recomendado para la programacin
asincrnica en .NET Framework 4 y en versiones posteriores.

MCT: Luis Dueas Pag 3 de 80


Modelos para la Programacin Asincrnica en .NET

1. Modelo asincrnico basado en tareas (TAP)


El modelo asincrnico basado en tareas (TAP) se basa en los tipos Task y Task<TResult> del espacio de nombres
System.Threading.Tasks, que se usan para representar operaciones asincrnicas arbitrarias. TAP es el modelo asincrnico
de diseo recomendado para el nuevo desarrollo.

Nombres, parmetros y tipos de valores devueltos


TAP usa un solo mtodo para representar el inicio y la finalizacin de una operacin asincrnica. Esto contrasta con el
modelo de programacin asincrnica (APM oIAsyncResult), que necesita mtodos Begin y End y, con el modelo
asincrnico basado en eventos (EAP), que necesita un mtodo que tenga el sufijo Async y tambin necesita uno o ms
eventos, tipos de delegado de controlador de eventos y tipos derivados de EventArg. Los mtodos asincrnicos de TAP
incluyen el sufijo Async despus del nombre de la operacin; por ejemplo, GetAsync para una operacin Get. Si va a
agregar un mtodo de TAP a una clase que ya contiene ese nombre de mtodo con el sufijo Async, use el
sufijo TaskAsync en su lugar. Por ejemplo, si la clase ya tiene un mtodo GetAsync, use el nombre GetTaskAsync.
El mtodo de TAP devuelve Task o Task<TResult>, en funcin de si el mtodo sincrnico correspondiente devuelve void
o un tipo TResult.
Los parmetros de un mtodo de TAP deben coincidir con los parmetros de su homlogo sincrnico y se deben
proporcionar en el mismo orden. Sin embargo, los parmetros out y ref estn exentos de esta regla y se deben evitar
completamente. En su lugar, los datos que se hubieran devuelto con un parmetro out o ref se deben devolver como
parte del tipo TResult devuelto por Task<TResult> y deben usar una tupla o una estructura de datos personalizada para
incluir varios valores. Los mtodos que estn dedicados exclusivamente a la creacin, manipulacin o combinacin de
tareas (donde el intento asincrnico del mtodo est claro en el nombre del mtodo o en el nombre del tipo al que el
mtodo pertenece) no necesitan seguir este modelo de nombres; esos mtodos se conocen a menudo
como combinadores (tambin denominados elementos de combinacin). Los ejemplos de combinadores incluyen
WhenAll y WhenAny, y se describen en la seccin que describe los combinadores integrados basadas en tareas
titulado Utilizar los elementos de combinacin basados en tareas integradas del artculo Utilizar el modelo asincrnico
basado en tareas.

Iniciar una operacin asincrnica


Un mtodo asincrnico basado en TAP puede hacer una pequea cantidad de trabajo sincrnicamente, como validar
argumentos e iniciar la operacin asincrnica, antes de que devuelva la tarea resultante. El trabajo sincrnico debe
reducirse al mnimo de modo que el mtodo asincrnico pueda volver rpidamente. Entre las razones para un retorno
rpido se incluyen las siguientes:
Los mtodos asincrnicos se pueden invocar desde subprocesos de la interfaz de usuario (UI) y cualquier
trabajo sincrnico de ejecucin prolongada puede daar la capacidad de respuesta de la aplicacin.
Se pueden iniciar varios mtodos asincrnicos simultneamente. Por tanto, cualquier trabajo de ejecucin
prolongada en la parte sincrnica de un mtodo asincrnico puede retrasar el inicio de otras operaciones
asincrnicas, lo que reduce las ventajas de la simultaneidad.
En algunos casos, la cantidad de trabajo necesario para completar la operacin es menor que la cantidad de trabajo
necesario para iniciar la operacin de forma asincrnica. La lectura de una secuencia donde la operacin de lectura se
puede satisfacer mediante datos que ya estn almacenados en bfer en la memoria es un ejemplo de este escenario. En
casos como este, la operacin puede completarse sincrnicamente y puede devolver una tarea que ya se ha
completado.

Excepciones
Un mtodo asincrnico debe generar una excepcin fuera de la llamada de mtodo asincrnico solo como respuesta a
un error de uso. Los errores de uso nunca deben producirse en cdigo de produccin. Por ejemplo, si al pasar una
referencia nula (Nothing en Visual Basic) como uno de los argumentos del mtodo se produce un estado de error
(representado normalmente por una excepcin ArgumentNullException), puede modificar el cdigo de llamada para
asegurarse de que nunca se pase una referencia nula. Para todos los dems errores, las excepciones que se producen
cuando se ejecuta un mtodo asincrnico deben asignarse a la tarea devuelta, aunque el mtodo asincrnico se
complete sincrnicamente antes de que se devuelva la tarea. Normalmente, una tarea contiene como mximo una
excepcin. Sin embargo, si la tarea representa varias operaciones (por ejemplo, WhenAll), se pueden asociar varias
excepciones a una nica tarea.

Entorno de destino
Cuando implementa un mtodo de TAP, puede determinar dnde se produce la ejecucin asincrnica. Puede elegir
ejecutar la carga de trabajo en el grupo de subprocesos, implementarla mediante E/S asincrnica (sin enlazarse a un
subproceso para la mayora de la ejecucin de la operacin), ejecutarla en un subproceso concreto (como el subproceso
de la interfaz de usuario) o usar cualquier nmero de contextos posibles. Un mtodo de TAP puede no tener nada que
ejecutar y puede devolver simplemente Task, que representa la existencia de una condicin en otra parte del sistema

MCT: Luis Dueas Pag 4 de 80


Modelos para la Programacin Asincrnica en .NET

(por ejemplo, una tarea que representa los datos que llegan a una estructura de datos en cola). El llamador del mtodo
de TAP puede bloquear la espera hasta que se complete el mtodo de TAP esperando sincrnicamente la tarea
resultante o puede ejecutar cdigo adicional (de continuacin) cuando la operacin asincrnica se complete. El creador
del cdigo de continuacin tiene control sobre lo que ese cdigo ejecuta. Puede crear el cdigo de continuacin
explcitamente, mediante mtodos de la clase Task (por ejemplo, ContinueWith) o implcitamente, usando la
compatibilidad con lenguaje sobre las continuaciones (por ejemplo, await en C#, Await en Visual Basic, AwaitValue en
F#).

Estado de la tarea
La clase Task proporciona un ciclo de vida para las operaciones asincrnicas y ese ciclo se representa mediante la
enumeracin TaskStatus. Para admitir los casos extremos de tipos que se derivan de Task y Task<TResult>, y para
admitir la separacin de la construccin de la programacin, la clase Task expone un mtodo Start. Las tareas creadas
por los constructores pblicos Task se denominan tareas en fro, porque inician su ciclo de vida en el estado Created no
programado y solo se programan cuando se llama a Start en estas instancias. Todas las dems tareas inician su ciclo de
vida en un estado activo, lo que significa que las operaciones asincrnicas que representan ya se han iniciado y su
estado de la tarea es un valor de enumeracin distinto de TaskStatus.Created. Todas las tareas que se devuelven de
mtodos de TAP deben estar activas. Si un mtodo de TAP usa internamente el constructor de una tarea para crear
instancias de la tarea que se va a devolver, el mtodo de TAP debe llamar a Start en el objeto Task antes de
devolverlo. Los consumidores de un mtodo de TAP pueden suponer con seguridad que la tarea devuelta est activa y
no deben intentar llamar a Start en ningn Task que se devuelve de un mtodo de TAP. La llamada a Start en una tarea
activa produce una excepcin InvalidOperationException.

Cancelacin (opcional)
En TAP, la cancelacin es opcional tanto para los implementadores de mtodo asincrnico como para los consumidores
de este mtodo. Si una operacin permite la cancelacin, expone una sobrecarga del mtodo asincrnico que acepta un
token de cancelacin (instancia de CancellationToken). Por convencin, el parmetro se denomina cancellationToken.
public Task ReadAsync(
byte [] buffer, int offset, int count,
CancellationToken cancellationToken);

La operacin asincrnica supervisa este token para las solicitudes de cancelacin. Si recibe una solicitud de cancelacin,
puede elegir admitir esa solicitud y cancelar la operacin. Si la solicitud de cancelacin hace que el trabajo finalice
prematuramente, el mtodo de TAP devuelve una tarea que finaliza en el estado Canceled; no hay ningn resultado
disponible y no se produce ninguna excepcin. El estado Canceled se considera un estado final (completado) para una
tarea, junto con los estados Faulted y RanToCompletion. Por tanto, si una tarea est en el estado Canceled, su
propiedad IsCompleted devuelve true. Cuando una tarea se completa en el estado Canceled, cualquier continuacin
registrada con la tarea se programa o se ejecuta, a menos que se especificara la opcin de continuacin
como NotOnCanceled para rechazar la continuacin. Cualquier cdigo que espera de forma asincrnica una tarea
cancelada mediante el uso de caractersticas del lenguaje sigue ejecutndose pero recibe un objeto Operation
CanceledException o una excepcin derivada del mismo. El cdigo que se bloquea sincrnicamente en espera de la
tarea mediante mtodos como Wait y WaitAll tambin contina ejecutndose con una excepcin.
Si un token de cancelacin ha solicitado la cancelacin antes de que se llame al mtodo de TAP que acepta ese token, el
mtodo de TAP debe devolver una tarea Canceled. Sin embargo, si se solicita la cancelacin mientras la operacin
asincrnica se ejecuta, la operacin asincrnica no necesita aceptar la solicitud de cancelacin. La tarea devuelta debe
finalizar en el estado Canceled slo si la operacin termina como resultado de la solicitud de cancelacin. Si se solicita la
cancelacin pero an se produce un resultado o una excepcin, la tarea debe finalizar en el estado RanTo
Completion o Faulted. Para los mtodos asincrnicos usados por un desarrollador que desea la cancelacin
principalmente, no tiene que proporcionar una sobrecarga que no acepte un token de cancelacin. Para los mtodos
que no pueden cancelarse, no proporcione sobrecargas que acepten un token de cancelacin; esto ayuda a indicar al
llamador si el mtodo de destino es realmente cancelable. El cdigo de consumidor que no desea la cancelacin puede
llamar a un mtodo que acepta un objeto CancellationToken y proporciona None como valor del argumento. None es
funcionalmente equivalente al objeto CancellationToken predeterminado.

Informe de progreso (opcional)


Algunas operaciones asincrnicas se benefician de proporcionar notificaciones de progreso; se suelen usar para
actualizar una interfaz de usuario con informacin sobre el progreso de la operacin asincrnica. En TAP, el progreso se
controla a travs de una interfaz IProgress<T>, la cual se pasa al mtodo asincrnico como parmetro
denominado progress. Proporcionar la interfaz de progreso cuando se llama al mtodo asincrnico ayuda a eliminar
condiciones de carrera resultantes de un uso incorrecto (es decir, cuando los controladores de eventos registrados
incorrectamente despus del inicio de la operacin pueden perder actualizaciones). Lo que es ms importante, la
interfaz de progreso admite implementaciones diferentes de progreso, segn determina el cdigo de uso. Por ejemplo,
el cdigo de uso puede encargarse solo de la ltima actualizacin de progreso, puede que desee almacenar en bfer

MCT: Luis Dueas Pag 5 de 80


Modelos para la Programacin Asincrnica en .NET

todas las actualizaciones o invocar una accin para cada actualizacin, o puede que desee controlar si se calculan las
referencias de la invocacin en un subproceso determinado. Todas estas opciones se pueden lograr utilizando otra
implementacin de la interfaz, personalizada segn las necesidades particulares del consumidor. Como ocurre con la
cancelacin, las implementaciones de TAP deben proporcionar un parmetro IProgress<T> solo si la API admite
notificaciones de progreso. Por ejemplo, si el mtodo ReadAsync anteriormente mencionado en este artculo puede
informar del progreso intermedio en forma de nmero de bytes ledos hasta el momento, la devolucin de llamada de
progreso puede ser una interfaz IProgress<T>:
public Task ReadAsync(
byte [] buffer, int offset, int count,
IProgress<long> progress);
Si un mtodo FindFilesAsync devuelve una lista de todos los archivos que renen un patrn particular de bsqueda,
la devolucin de progreso puede proporcionar una estimacin del porcentaje de trabajo completado as como el
conjunto de resultados parciales. Puede hacerlo con una tupla:
public Task<ReadOnlyCollection<FileInfo>> FindFilesAsync(
string pattern,
IProgress<Tuple<double,ReadOnlyCollection<List<FileInfo>>>> progress);

o con un tipo de datos que es especfico de la API:


public Task<ReadOnlyCollection<FileInfo>> FindFilesAsync(
string pattern,
IProgress<FindFilesProgressInfo> progress);
En este ltimo caso, el tipo de datos especial suele tener el sufijo ProgressInfo.
Si las implementaciones de TAP proporcionan sobrecargas que aceptan un parmetro progress, deben permitir que el
argumento sea null, en cuyo caso no se notifica ningn progreso. Las implementaciones de TAP deben notificar el
progreso al objeto Progress<T> sincrnicamente, lo cual permite al mtodo asincrnico proporcionar rpidamente el
progreso y hacer que el consumidor del progreso determine cmo y dnde es mejor controlar la informacin. Por
ejemplo, la instancia de progreso puede elegir hacerse con las devoluciones de llamada y provocar eventos en un
contexto capturado de sincronizacin.

Implementaciones IProgress<t>
.NET Framework 4.5 proporciona una nica implementacin de IProgress<T>: Progress<T>. Se declara la clase Progress
<T> como se indica a continuacin:
public class Progress<T> : IProgress<T>
{
public Progress();
public Progress(Action<T> handler);
protected virtual void OnReport(T value);
public event EventHandler<T> ProgressChanged;
}
Una instancia de Progress<T> expone un evento ProgressChanged, que se provoca cada vez que la operacin
asincrnica informe de una actualizacin de progreso. El evento ProgressChanged se genera en el
objeto SynchronizationContext que se captur cuando se cre la instancia de Progress<T>. Si no haba ningn contexto
de sincronizacin disponible, se usa un contexto predeterminado destinado al grupo de subprocesos. Los controladores
pueden registrarse con este evento. Un nico controlador tambin puede ser proporcionado al
constructor Progress<T> por comodidad y se comporta como un controlador de eventos para el evento Progress
Changed. Las actualizaciones de progreso se generan de forma asincrnica para evitar retrasar la operacin asincrnica
mientras los controladores de eventos se ejecutan. Otra implementacin IProgress<T> podra elegir aplicarse para
diferentes semnticas.

Elegir las sobrecargas que se van a proporcionar


Si una implementacin TAP utiliza CancellationToken opcional y los parmetros opcionales IProgress<T>, podra
requerir hasta cuatro sobrecargas:
public Task MethodNameAsync();
public Task MethodNameAsync(, CancellationToken cancellationToken);
public Task MethodNameAsync(, IProgress<T> progress);
public Task MethodNameAsync(,
CancellationToken cancellationToken, IProgress<T> progress);

Sin embargo, muchas implementaciones TAP no proporcionan ni la cancelacin ni las capacidades de progreso, por lo
que requieren un nico mtodo:

MCT: Luis Dueas Pag 6 de 80


Modelos para la Programacin Asincrnica en .NET

public Task MethodNameAsync();

Si una implementacin de TAP admite cancelacin o progreso pero no ambos, puede proporcionar dos sobrecargas:
public Task MethodNameAsync();
public Task MethodNameAsync(, CancellationToken cancellationToken);
// or
public Task MethodNameAsync();
public Task MethodNameAsync(, IProgress<T> progress);
Si una implementacin TAP admite cancelacin y progreso, puede exponer las cuatro sobrecargas. Sin embargo, puede
proporcionar slo los dos siguientes:
public Task MethodNameAsync();
public Task MethodNameAsync(,
CancellationToken cancellationToken, IProgress<T> progress);
Para compensar las dos combinaciones intermedias que faltan, los desarrolladores pueden pasar None o un Cancellation
Token predeterminado para el parmetrocancellationToken y null para el parmetro progress.
Si se espera que cada uso del mtodo TAP admita cancelacin o progreso, puede omitir las sobrecargas que no acepten
el parmetro pertinente.
Si se decide exponer varias sobrecargas para crear la cancelacin o para el progreso opcional, las sobrecargas que no
admitan cancelacin o progreso deben comportarse como si pasaran None para cancelacin o null para el progreso en
la sobrecarga que admite ambas.

1.1. Implementar el modelo asincrnico basado en tareas


Puede implementar el modelo asincrnico basado en tareas (TAP) de tres maneras: mediante los compiladores de C# y
Visual Basic en Visual Studio, manualmente o mediante una combinacin del compilador y mtodos manuales. En las
siguientes secciones se describe cada mtodo con detalle. Puede usar el modelo de TAP para implementar operaciones
asincrnicas enlazadas a clculos y enlazadas a E/S; en la seccin Cargas de trabajo se describe cada tipo de operacin.

Generar mtodos de TAP


Usar los compiladores
En Visual Studio 2012 y .NET Framework 4.5, cualquier mtodo que tenga la palabra clave async (Async en Visual Basic)
se considera un mtodo asincrnico, y los compiladores de C# y Visual Basic realizan las transformaciones necesarias
para implementar el mtodo de forma asincrnica mediante TAP. Un mtodo asincrnico debe devolver un
objeto Task o Task<TResult>. En el ltimo caso, el cuerpo de la funcin debe devolver TResult y el compilador asegura
que este resultado est disponible a travs del objeto de la tarea resultante. Del mismo modo, en la tarea de salida se
calculan las referencias de cualquier excepcin no controlada dentro del cuerpo del mtodo y esto hace que la tarea
resultante finalice en el estado Faulted. La excepcin es cuando un objeto OperationCanceledException (o un tipo
derivado) no est controlado, en cuyo caso la tarea resultante finaliza en el estado Canceled.

Generar mtodos de TAP manualmente


Puede implementar el modelo de TAP manualmente para tener un mejor control sobre la implementacin. El
compilador se basa en la superficie pblica expuesta del espacio de nombres System.Threading.Tasks y los tipos
auxiliares del espacio de nombres System.Runtime.CompilerServices. Para implementar TAP personalmente, cree un
objeto TaskCompletionSource<TResult>, realice la operacin asincrnica y, cuando se complete, llame al
mtodo SetResult, SetException o SetCanceled, o a la versin Tryde uno de estos mtodos. Cuando implementa un
mtodo de TAP manualmente, debe completar la tarea resultante cuando la operacin asincrnica representada se
complete. Por ejemplo:
public static Task<int> ReadTask(this Stream stream, byte[] buffer, int
offset, int count, object state)
{
var tcs = new TaskCompletionSource<int>();
stream.BeginRead(buffer, offset, count, ar =>
{
try { tcs.SetResult(stream.EndRead(ar)); }
catch (Exception exc) { tcs.SetException(exc); }
}, state);
return tcs.Task;
}
Enfoque hbrido
Puede resultar til implementar el modelo de TAP manualmente pero delegar la lgica bsica de la implementacin en
el compilador. Por ejemplo, quizs desee usar el enfoque hbrido cuando desee comprobar argumentos fuera de un

MCT: Luis Dueas Pag 7 de 80


Modelos para la Programacin Asincrnica en .NET

mtodo asincrnico generado por el compilador de forma que las excepciones puedan salir del llamador directo del
mtodo en lugar de exponerse a travs del objeto Task:
public Task<int> MethodAsync(string input)
{
if (input == null) throw new ArgumentNullException("input");
return MethodAsyncInternal(input);
}

private async Task<int> MethodAsyncInternal(string input)


{
// code that uses await goes here
return value;
}
Otro caso donde es til esa delegacin es cuando implementa la optimizacin de acceso rpido y desea devolver una
tarea almacenada en memoria cach.

Cargas de trabajo
Puede implementar operaciones asincrnicas enlazadas a clculos y enlazadas a E/S como mtodos de TAP. Sin
embargo, cuando los mtodos de TAP se exponen pblicamente desde una biblioteca, solo se deben suministrar para
cargas de trabajo que impliquen operaciones enlazadas a E/S (tambin pueden implican clculos, pero no deben ser
estrictamente de clculo). Si un mtodo est enlazado a clculos puramente, solo se debe exponer como una
implementacin sincrnica; el cdigo que lo usa puede elegir si ajustar una invocacin de ese mtodo sincrnico en una
tarea para descargar el trabajo en otro subproceso o para lograr el paralelismo.

Tareas enlazadas a clculos


La clase Task es idnea para representar operaciones intensivas en clculos. De forma predeterminada, se beneficia de la
compatibilidad especial dentro de la clase ThreadPool para proporcionar una ejecucin eficaz, y tambin proporciona un
buen control sobre cundo, dnde y cmo se ejecutan los clculos asincrnicos. Puede generar tareas enlazadas a
clculos de las maneras siguientes:
En .NET Framework 4, use el mtodo TaskFactory.StartNew, que acepta un delegado (normalmente
Action<T> o Func<TResult>) que se va a ejecutar de forma asincrnica. Si proporciona un delegado
de Action<T>, el mtodo devuelve un objeto Task que representa la ejecucin asincrnica de ese delegado. Si
proporciona un delegado de Func<TResult>, el mtodo devuelve un objeto Task<TResult>. Las sobrecargas
del mtodo StartNew aceptan un token de cancelacin (CancellationToken), las opciones de creacin de la
tarea (TaskCreationOptions) y un programador de tareas (TaskScheduler), todo lo cual proporciona un control
especfico sobre la programacin y la ejecucin de la tarea. Una instancia de generador que tiene como
destino el programador de tareas actual est disponible como una propiedad esttica (Factory) de la
clase Task; por ejemplo: Task.Factory.StartNew().
En .NET Framework 4.5, use el mtodo esttico Task.Run como acceso directo a TaskFactory.StartNew. Puede
usar Run para iniciar fcilmente una tarea enlazada a clculos destinada al grupo de subprocesos. En .NET
Framework 4.5, este es el mecanismo preferido para iniciar una tarea enlazada a clculos. Utilice StartNew
directamente cuando desea un control ms detallado sobre la tarea.
Use los constructores del tipo Task o el mtodo Start si desea generar y programar la tarea por separado. Los
mtodos pblicos solo deben devolver tareas que ya se han iniciado.
Use las sobrecargas del mtodo Task.ContinueWith. Este mtodo crea una nueva tarea que se programa
cuando se completa otra tarea. Algunas de las sobrecargas de ContinueWith aceptan un token de cancelacin,
opciones de continuacin y un programador de tareas para tener un mejor control sobre la programacin y la
ejecucin de la tarea de continuacin.
Utilice los mtodos TaskFactory.ContinueWhenAll y TaskFactory.ContinueWhenAny. Estos mtodos crean una
nueva tarea que se programa cuando se completa todo o cualquier conjunto de tareas proporcionado. Estos
mtodos tambin proporcionan sobrecargas para controlar la programacin y la ejecucin de estas tareas.
En las tareas enlazadas a clculos, el sistema puede evitar la ejecucin de una tarea programada si recibe una solicitud
de cancelacin antes de que comience la ejecucin de la tarea. Por tanto, si proporciona un token de cancelacin
(objeto CancellationToken), puede pasar ese token al cdigo asincrnico que supervisa el token. Tambin puede
proporcionar el token a uno de los mtodos mencionados previamente como StartNew o Run para que el runtime
de Task tambin supervise el token.
Por ejemplo, considere un mtodo asincrnico que presenta una imagen. El cuerpo de la tarea puede sondear el token
de cancelacin para que el cdigo pueda salir pronto si llega una solicitud de cancelacin durante la
representacin. Adems, si la solicitud de cancelacin llega antes de que se inicie la representacin, desear evitar la
operacin de representacin:
internal Task<Bitmap> RenderAsync(
ImageData data, CancellationToken cancellationToken)

MCT: Luis Dueas Pag 8 de 80


Modelos para la Programacin Asincrnica en .NET

{
return Task.Run(() =>
{
var bmp = new Bitmap(data.Width, data.Height);
for(int y=0; y<data.Height; y++)
{
cancellationToken.ThrowIfCancellationRequested();
for(int x=0; x<data.Width; x++)
{
// render pixel [x,y] into bmp
}
}
return bmp;
}, cancellationToken);
}
Las tareas enlazadas a clculos finalizan en un estado Canceled si se cumple al menos una de las condiciones siguientes:
Llega una solicitud de cancelacin a travs del objeto CancellationToken, que se proporciona como
argumento al mtodo de creacin (por ejemplo, StartNew o Run) antes de que la tarea cambie al
estado Running.
Una excepcin OperationCanceledException no est controlada dentro del cuerpo de esta tarea, esa
excepcin contiene el mismo objeto CancellationToken que se pasa a la tarea y ese token muestra que se
solicit la cancelacin.
Si hay otra excepcin no controlada en el cuerpo de la tarea, la tarea finaliza en el estado Faulted y cualquier intento de
esperar en la tarea u obtener acceso a su resultado produce una excepcin.

Tareas enlazadas a E/S


Para crear una tarea que no se deba respaldar directamente por un subproceso durante toda su ejecucin, use el
tipo TaskCompletionSource<TResult>. Este tipo expone una propiedad Task que devuelve una instancia asociada
de Task<TResult>. El ciclo de vida de esta tarea se controla mediante mtodos TaskCompletionSource<TResult>
como SetResult, SetException, SetCanceled y sus variantes de TrySet.
Suponga que desea crear una tarea que se completar despus de un perodo de tiempo especificado. Por ejemplo,
puede que desee retrasar una actividad en la interfaz de usuario. La clase System.Threading.Timer ya proporciona la
capacidad de invocar de forma asincrnica un delegado despus de un perodo de tiempo especificado, y
mediante TaskCompletionSource<TResult> puede colocar un objeto Task<TResult> delante del temporizador, por
ejemplo:
public static Task<DateTimeOffset> Delay(int millisecondsTimeout)
{
TaskCompletionSource<DateTimeOffset> tcs = null;
Timer timer = null;
timer = new Timer(delegate
{
timer.Dispose();
tcs.TrySetResult(DateTimeOffset.UtcNow);
}, null, Timeout.Infinite, Timeout.Infinite);
tcs = new TaskCompletionSource<DateTimeOffset>(timer);
timer.Change(millisecondsTimeout, Timeout.Infinite);
return tcs.Task;
}
A partir de .NET Framework 4.5, el mtodo Task.Delay se proporciona con este propsito y puede usarlo dentro de otro
mtodo asincrnico, por ejemplo, para implementar un bucle asincrnico de sondeo:
public static async Task Poll(Uri url, CancellationToken cancellationToken,
IProgress<bool> progress)
{
while(true)
{
await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken);
bool success = false;
try
{
await DownloadStringAsync(url);
success = true;
}
catch { /* ignore errors */ }

MCT: Luis Dueas Pag 9 de 80


Modelos para la Programacin Asincrnica en .NET

progress.Report(success);
}
}
La clase TaskCompletionSource<TResult> no tiene ningn homlogo no genrico. Sin embargo, Task<TResult> deriva
de Task, por lo que puede usar el objeto genricoTaskCompletionSource<TResult> para los mtodos enlazados a E/S
que simplemente devuelven una tarea. Para ello, puede usar un origen con un TResult ficticio (Boolean es una buena
opcin predeterminada, pero si le preocupa que el usuario de Task lo convierta en tipos inferiores a un
objeto Task<TResult>, puede usar un tipo TResultprivado en su lugar). Por ejemplo, el mtodo Delay del ejemplo
anterior devuelve la hora actual junto con desplazamiento resultante (Task<DateTimeOffset>). Si el valor de resultado
es innecesario, el mtodo podra codificarse en su lugar como sigue (observe el cambio del tipo de valor devuelto y el
cambio del argumento a TrySetResult):
public static Task<bool> Delay(int millisecondsTimeout)
{
TaskCompletionSource<bool> tcs = null;
Timer timer = null;

timer = new Timer(delegate


{
timer.Dispose();
tcs.TrySetResult(true);
}, null, Timeout.Infinite, Timeout.Infinite);

tcs = new TaskCompletionSource<bool>(timer);


timer.Change(millisecondsTimeout, Timeout.Infinite);
return tcs.Task;
}
Tareas enlazadas a clculos y enlazadas a E/S mixtas
Los mtodos asincrnicos no se limitan solo a operaciones enlazadas a clculos o enlazadas a E/S pero pueden
representar una combinacin de ambas. De hecho, se suelen combinar varias operaciones asincrnicas en operaciones
mixtas mayores. Por ejemplo, el mtodo RenderAsync en un ejemplo anterior realiz una operacin de computacin
intensiva para generar una imagen segn alguna entrada imageData. Este imageData podra proceder de un servicio
Web al que tiene acceso de forma asincrnica:
public async Task<Bitmap> DownloadDataAndRenderImageAsync(
CancellationToken cancellationToken)
{
var imageData = await DownloadImageDataAsync(cancellationToken);
return await RenderAsync(imageData, cancellationToken);
}
En este ejemplo tambin se muestra cmo se puede incluir en un subproceso un nico token de cancelacin mediante
varias operaciones asincrnicas.

1.2. Utilizar el modelo asincrnico basado en tareas


Cuando se utiliza el patrn asincrnico basado en tareas (TAP) para ejecutar operaciones asincrnicas, se pueden utilizar
devoluciones de llamada para conseguir esperas sin bloqueos. Para las tareas, esto se logra con mtodos
como Task.ContinueWith. La compatibilidad asincrnica basada en lenguajes oculta devoluciones de llamada al permitir
que las operaciones asincrnicas sean esperadas dentro del flujo de control normal y el cdigo generado por el
compilador proporcione esta misma compatibilidad a nivel de API.

Suspender la ejecucin con Await


Empezando con .NET Framework 4.5, se puede utilizar la palabra clave await (Referencia de C#) en C# y Await
(Operador) (Visual Basic) en Visual Basic para esperar asincrnicamente a Task y a los objetos Task<TResult>. Cuando se
est esperando un Task, la expresin await es de tipo void. Cuando se est esperando un Task<TResult>, la
expresin await es de tipo TResult. Una expresin await debe aparecer en el cuerpo de un mtodo asincrnico.
Ms en detalle, la funcin de espera instala una devolucin de llamada en la tarea mediante una continuacin. Esta
devolucin de llamada reanuda el mtodo asincrnico en el momento de suspensin. Cuando se reanuda el mtodo
asincrnico, si la operacin en espera se completa correctamente y fue Task<TResult>, se devuelve su TResult.
Si Task o Task<TResult> esperado termin en el estado Canceled, se lanza una excepcin Operation CanceledException.
Si Task o Task<TResult> esperado termin en el estado Faulted, se lanza la excepcin que produjo el error. Task puede
darse como resultado de varias excepciones, pero slo una de estas excepciones se propaga.Sin embargo, la
propiedad Task.Exception devuelve una excepcin AggregateException que contiene todos los errores.

MCT: Luis Dueas Pag 10 de 80


Modelos para la Programacin Asincrnica en .NET

Si un contexto de sincronizacin (objetoSynchronizationContext) est asociado al subproceso que estaba ejecutando el


mtodo asincrnico en el momento de la suspensin (por ejemplo, si la propiedad SynchronizationContext.Current no
es null), el mtodo asincrnico se reanuda en el mismo contexto de sincronizacin utilizando el mtodoPost del
contexto. De lo contrario, se basa en el programador de tareas (objetoTaskScheduler) que estaba en curso en el
momento de la suspensin. Normalmente, ste es el programador de tareas predeterminado (TaskScheduler.Default),
que tiene como destino el grupo de subprocesos. Este programador de tareas determina si la operacin asincrnica
esperada debe reanudarse donde se complet o si la reanudacin debe programarse. El programador predeterminado
suele permitir que la continuacin se ejecute en el subproceso que la operacin en espera complet.
Cuando se invoca un mtodo asincrnico, se ejecuta sincrnicamente el cuerpo de la funcin hasta la primera expresin
en espera de una instancia esperable que an no se ha completado, punto en el que la invocacin vuelve al llamador. Si
el mtodo asincrnico no devuelve void, se devuelve un objeto Task o Task<TResult> para representar el clculo en
curso. En un mtodo asincrnico que no es void, si se encuentra una instruccin de retorno, o se alcanza el final del
cuerpo del mtodo, la tarea se completa en el estado final RanToCompletion. Si una excepcin no controlada hace que
el control deje el cuerpo del mtodo asincrnico, la tarea termina en el estado Faulted. Si la excepcin
es OperationCanceledException, la tarea finaliza en el estado Canceled. De esta manera, el resultado o la excepcin se
publican finalmente.
Hay muchas variaciones importantes de este comportamiento. Por razones de rendimiento, si una tarea ya se ha
completado cuando se espera, el control no se cede y la funcin sigue ejecutndose. Adems, volver al contexto original
no siempre es el comportamiento deseado y se puede modificar; esto se describe con ms detalle en la seccin
siguiente.

Configurar la suspension y reanudacin con Yield y ConfigureAwait


Varios son los mtodos que proporcionan mayor control sobre la ejecucin de un mtodo asincrnico. Por ejemplo, se
puede utilizar el mtodo Task.Yield para incluir un punto de produccin en el mtodo asincrnico:
public class Task :
{
public static YieldAwaitable Yield();

}
Esto es equivalente a enviar de forma asincrnica o a programar de nuevo el contexto actual.
Task.Run(async delegate
{
for(int i=0; i<1000000; i++)
{
await Task.Yield(); // fork the continuation into a separate work item
...
}
});
Tambin se puede utilizar el mtodo Task.ConfigureAwait para un mejor control sobre la suspensin y la reanudacin de
un mtodo asincrnico. Como se ha mencionado anteriormente, de manera predeterminada el contexto actual se
captura en el momento en que se suspende un mtodo asincrnico y ese contexto capturado se utiliza para invocar la
continuacin del mtodo asincrnico en la reanudacin. En muchos casos, ste es el comportamiento exacto que se
desea. En otros casos, puede no interesar saber el contexto de continuacin y se puede mejorar el rendimiento si se
evita que tales elementos vuelvan al contexto original. Para habilitar esto, utilice el mtodo Task.ConfigureAwait para
informar a la operacin en espera de que no capture ni reanude el contexto, sino que contine la ejecucin siempre que
la operacin asincrnica en espera se complete:
await someTask.ConfigureAwait(continueOnCapturedContext:false);

Cancelar una operacin asincrnica


Empezando con .NET Framework 4, los mtodos TAP que admiten cancelacin proporcionan al menos una sobrecarga
que acepta un token de cancelacin (objeto CancellationToken).
El token de cancelacin se crea desde el origen de tokens de cancelacin (objeto CancellationTokenSource). La
propiedad Token del origen devuelve el token de cancelacin que se notificar cuando se llame al mtodo Cancel del
origen. Por ejemplo, si se desea descargar una sola pgina web y poder cancelar la operacin, se crea un
objeto CancellationTokenSource, se pasa el token al mtodo TAP y despus se llama al mtodo Cancel cuando est
preparado para cancelar la operacin:
var cts = new CancellationTokenSource();
string result = await DownloadStringAsync(url, cts.Token);
// at some point later, potentially on another thread
cts.Cancel();

MCT: Luis Dueas Pag 11 de 80


Modelos para la Programacin Asincrnica en .NET

Para cancelar llamadas asincrnicas mltiples, se puede pasar el mismo token a todas las invocaciones:
var cts = new CancellationTokenSource();
IList<string> results = await Task.WhenAll(from url in urls
select DownloadStringAsync(url, cts.Token));
// at some point later, potentially on another thread
cts.Cancel();
O bien, se puede pasar el mismo token a un subconjunto selectivo de operaciones:
var cts = new CancellationTokenSource();
byte [] data = await DownloadDataAsync(url, cts.Token);
await SaveToDiskAsync(outputPath, data, CancellationToken.None);
// at some point later, potentially on another thread
cts.Cancel();
Las solicitudes de cancelacin se pueden iniciar desde cualquier subproceso.
Se puede pasar el valor CancellationToken.None a cualquier mtodo que acepte un token de cancelacin para indicar
que la cancelacin nunca se solicitar. Esto hace que la propiedad CancellationToken.CanBeCanceled devuelva false y
consiguientemente el mtodo llamado pueda optimizarse. Con fines de prueba, tambin se puede pasar un token
precancelado de cancelacin del que se creen instancias utilizando el constructor que acepta un valor booleano para
indicar si el token debera comenzar en un estado ya cancelado o no cancelable.
Este tratamiento de la cancelacin tiene varias ventajas:
Se puede pasar el mismo token de cancelacin a cualquier nmero de operaciones asincrnicas o sincrnicas.
La misma solicitud de cancelacin puede extenderse a cualquier nmero de oyentes.
El desarrollador de la API asincrnica tiene completo control de si la cancelacin puede ser solicitada y cundo
puede tener lugar.
El consumidor de la API puede determinar selectivamente las llamadas asincrnicas a las que las solicitudes de
cancelacin se propagarn.

Supervisar el progreso
Algunos mtodos asincrnicos presentan su progreso a travs de una interfaz de progreso que pasa por el mtodo
asincrnico. Por ejemplo, piense en una funcin que de forma asincrnica descarga una cadena de texto y en el camino
provoca actualizaciones de progreso que incluyen el porcentaje de descarga que se ha completado hasta el
momento. Este mtodo se puede utilizar en una aplicacin de Windows Presentation Foundation (WPF) como se indica
a continuacin:
private async void btnDownload_Click(object sender, RoutedEventArgs e)
{
btnDownload.IsEnabled = false;
try
{
txtResult.Text = await DownloadStringAsync(txtUrl.Text,
new Progress<int>(p => pbDownloadProgress.Value = p));
}
finally { btnDownload.IsEnabled = true; }
}

Utilizar los elementos de combinacin basados en tareas integradas


El espacio de nombres System.Threading.Tasks incluye varios mtodos para componer y trabajar con tareas.

Task.Run
La clase Task incluye varios mtodos Run que permiten con facilidad librarse de trabajo como un Task o Task <TResult>
al grupo de subprocesos, por ejemplo:
public async void button1_Click(object sender, EventArgs e)
{
textBox1.Text = await Task.Run(() =>
{
// do compute-bound work here
return answer;
});
}
Algunos de estos mtodos Run tales como la sobrecarga de Task.Run(Func<Task>) existen como una versin reducida
del mtodo TaskFactory.StartNew. Otras sobrecargas, como Task.Run(Func<Task>), permiten que la espera se utilice
dentro del trabajo descargado, por ejemplo:

MCT: Luis Dueas Pag 12 de 80


Modelos para la Programacin Asincrnica en .NET

public async void button1_Click(object sender, EventArgs e)


{
pictureBox1.Image = await Task.Run(async() =>
{
using(Bitmap bmp1 = await DownloadFirstImageAsync())
using(Bitmap bmp2 = await DownloadSecondImageAsync())
return Mashup(bmp1, bmp2);
});
}
Dichas sobrecargas son lgicamente equivalentes a utilizar el mtodo TaskFactory.StartNew en conjuncin con el
mtodo de extensin Unwrap en la biblioteca de tareas paralelas.

Task.FromResult
Para esos escenarios en los que los datos pueden estar ya disponibles y simplemente necesitan ser devueltos desde un
mtodo que devuelve tareas subido en un FromResult<TResult>, el mtodo Task<TResult> puede ser utilizado:
public Task<int> GetValueAsync(string key)
{
int cachedValue;
return TryGetCachedValue(out cachedValue) ?
Task.FromResult(cachedValue) : GetValueAsyncInternal();
}

private async Task<int> GetValueAsyncInternal(string key)


{

}

Task.WhenAll
Utilice el mtodo WhenAll de forma asincrnica para atender varias operaciones asincrnicas que se representan como
tareas. El mtodo tiene mltiples sobrecargas que admiten un conjunto de tareas no genricas o un conjunto no
uniforme de tareas genricas (por ejemplo, tareas que esperan asincrnicamente varias operaciones de retorno tipo
void, o tareas que esperan asincrnicamente mltiples mtodos de valores de retorno en los que cada valor puede ser
de diferente tipo) as como tambin admiten un conjunto uniforme de tareas genricas (tales como esperar
asincrnicamente varios mtodos de retorno TResult).
Digamos que se desea enviar un correo electrnico a varios clientes Se puede solapar el envo de mensajes para que no
se tenga que esperar a que un mensaje se complete antes de enviar el siguiente. Tambin se puede averiguar qu
operaciones de envo se han completado y si se ha producido algn error.
IEnumerable<Task> asyncOps = from addr in addrs select SendMailAsync(addr);
await Task.WhenAll(asyncOps);
Este cdigo no controla explcitamente las excepciones que se pueden producir, sino que permite que las excepciones
se propaguen fuera de await en la tarea resultante de WhenAll. Para controlar las excepciones, se puede usar el
siguiente cdigo:
IEnumerable<Task> asyncOps = from addr in addrs select SendMailAsync(addr);
try
{
await Task.WhenAll(asyncOps);
}
catch(Exception exc)
{
...
}
En este caso, si una operacin asincrnica da error, todas las excepciones se consolidarn en una
excepcin AggregateException, que se almacena en Task que se devuelve desde el mtodo WhenAll. Sin embargo, slo
una de esas excepciones se propaga por la palabra clave await. Si se desean examinar todas las excepciones, se puede
volver a escribir el cdigo anterior como sigue:
Task [] asyncOps = (from addr in addrs select SendMailAsync(addr)).ToArray();
try
{
await Task.WhenAll(asyncOps);
}
catch(Exception exc)
{

MCT: Luis Dueas Pag 13 de 80


Modelos para la Programacin Asincrnica en .NET

foreach(Task faulted in asyncOps.Where(t => t.IsFaulted))


{
// work with faulted and faulted.Exception
}
}
Considere el caso de cmo descargar varios archivos de la web de forma asincrnica. En este caso, todas las operaciones
asincrnicas tienen tipos de resultado homogneos y el acceso a los resultados es simple:
string [] pages = await Task.WhenAll(
from url in urls select DownloadStringAsync(url));
Como en el caso anterior que se devuelve void, las mismas tcnicas de control de excepciones se pueden utilizar aqu:
Task [] asyncOps =
(from url in urls select DownloadStringAsync(url)).ToArray();
try
{
string [] pages = await Task.WhenAll(asyncOps);
...
}
catch(Exception exc)
{
foreach(Task<string> faulted in asyncOps.Where(t => t.IsFaulted))
{
// work with faulted and faulted.Exception
}
}

Task.WhenAny
Se puede usar el mtodo WhenAny para esperar de forma asincrnica a una de las mltiples operaciones asincrnicas
representadas como tareas a completar. Este mtodo tiene cuatro usos principales:
Redundancia: Efectuar mltiples veces una operacin y seleccionar la que se complete primero (por ejemplo:
conectar con mltiples servicios web de cotizacin de valores que producirn un solo resultado y seleccionar
el que se complete ms rpidamente).
Intercalado: Iniciar mltiples operaciones y esperar a que todas se completen, pero procesarlas a medida que
se van completando.
Regulacin: Permitir que las operaciones adicionales comiencen cuando las otras se completen. Esto es una
extensin del escenario de intercalado.
Rescate anticipado: Por ejemplo, una operacin representada por la tarea t1 se puede agrupar en una
tarea WhenAny con otra tarea t2 y se puede esperar la tarea WhenAny. La tarea t2 podra representar un
tiempo de espera, o cancelacin, o alguna otra seal que haga que la tarea WhenAny se complete antes de
completarse t1.
Redundancia
Considere un caso en el que se desea tomar una decisin sobre si adquirir un valor o no. Hay varios servicios web de
recomendacin de valores en los que se confa, pero dependiendo de la carga diaria, cada uno de los servicios puede
resultar bastante lento en momentos diferentes. Se puede usar el mtodo WhenAny para recibir una notificacin cuando
cualquier operacin se complete:
var recommendations = new List<Task<bool>>()
{
GetBuyRecommendation1Async(symbol),
GetBuyRecommendation2Async(symbol),
GetBuyRecommendation3Async(symbol)
};
Task<bool> recommendation = await Task.WhenAny(recommendations);
if (await recommendation) BuyStock(symbol);
A diferencia de WhenAll, que devuelve los resultados empaquetados de todas las tareas que se completan
correctamente, WhenAny devuelve la tarea que se complet. Si una tarea da error, es importante saber dnde est el
error, y si acaba correctamente, es importante saber a qu tarea est asociado el valor devuelto. Por consiguiente, es
necesario tener acceso al resultado de la tarea devuelta, o esperar an ms, como se muestra en este ejemplo.
Al igual que con WhenAll, se necesita poder alojar excepciones. Debido a que se recibe de vuelta la tarea completada, se
puede esperar que la tarea devuelta tenga los errores propagados, y hacer try/catch sobre estos correctamente; por
ejemplo:
Task<bool> [] recommendations = ;
while(recommendations.Count > 0)

MCT: Luis Dueas Pag 14 de 80


Modelos para la Programacin Asincrnica en .NET

{
Task<bool> recommendation = await Task.WhenAny(recommendations);
try
{
if (await recommendation) BuyStock(symbol);
break;
}
catch(WebException exc)
{
recommendations.Remove(recommendation);
}
}
Adems, an en el caso de que una primera tarea se complete correctamente, las tareas posteriores pueden producir
errores. En este punto, son varias las opciones para abordar las excepciones: se puede esperar a que se completen todas
las tareas lanzadas, en cuyo caso se puede usar el mtodo WhenAll; o bien, se puede decidir que todas las excepciones
son importantes y se deben registrar. Para ello, se pueden utilizar directamente las continuaciones para recibir una
notificacin cuando las tareas estn completadas de forma asincrnica:
foreach(Task recommendation in recommendations)
{
var ignored = recommendation.ContinueWith(
t => { if (t.IsFaulted) Log(t.Exception); });
}
O bien
foreach(Task recommendation in recommendations)
{
var ignored = recommendation.ContinueWith(
t => Log(t.Exception), TaskContinuationOptions.OnlyOnFaulted);
}
o incluso:
private static async void LogCompletionIfFailed(IEnumerable<Task> tasks)
{
foreach(var task in tasks)
{
try { await task; }
catch(Exception exc) { Log(exc); }
}
}

LogCompletionIfFailed(recommendations);
Finalmente, se puede desear cancelar todas las operaciones restantes.
var cts = new CancellationTokenSource();
var recommendations = new List<Task<bool>>()
{
GetBuyRecommendation1Async(symbol, cts.Token),
GetBuyRecommendation2Async(symbol, cts.Token),
GetBuyRecommendation3Async(symbol, cts.Token)
};
Task<bool> recommendation = await Task.WhenAny(recommendations);
cts.Cancel();
if (await recommendation) BuyStock(symbol);

Intercalado
Considere el caso en el que se descarguen imgenes de la web y se procese cada imagen (por ejemplo, agregar la
imagen a un control de UI). Se tiene que hacer el procesado secuencialmente en el subproceso de UI, pero se desea que
las imgenes se descarguen de forma simultnea en la medida de lo posible. Adems, no se desea esperar a agregar las
imgenes a la UI hasta que se hayan descargado todas (se desean agregar a medida que se completan):
List<Task<Bitmap>> imageTasks =
(from imageUrl in urls select GetBitmapAsync(imageUrl)).ToList();
while(imageTasks.Count > 0)
{
try

MCT: Luis Dueas Pag 15 de 80


Modelos para la Programacin Asincrnica en .NET

{
Task<Bitmap> imageTask = await Task.WhenAny(imageTasks);
imageTasks.Remove(imageTask);

Bitmap image = await imageTask;


panel.AddImage(image);
}
catch{}
}
El mismo intercalado tambin se puede aplicar a un escenario que implica procesamiento de cmputo intensivo en
el ThreadPool de imgenes descargadas; por ejemplo:
List<Task<Bitmap>> imageTasks =
(from imageUrl in urls select GetBitmapAsync(imageUrl)
.ContinueWith(t => ConvertImage(t.Result)).ToList();
while(imageTasks.Count > 0)
{
try
{
Task<Bitmap> imageTask = await Task.WhenAny(imageTasks);
imageTasks.Remove(imageTask);
Bitmap image = await imageTask;
panel.AddImage(image);
}
catch{}
}

Limitacin de peticiones
Piense en el ejemplo de intercalado, excepto que el usuario ahora est descargando tantas imgenes que las descargas
necesitan que se limiten explcitamente; por ejemplo, slo se desea realizar un nmero especfico de descargas
simultneamente. Para lograr esto, se puede iniciar un subconjunto de las operaciones asincrnicas. A medida que las
operaciones se completan, se pueden iniciar operaciones adicionales para reemplazarlas:
const int CONCURRENCY_LEVEL = 15;
Uri [] urls = ;
int nextIndex = 0;
var imageTasks = new List<Task<Bitmap>>();
while(nextIndex < CONCURRENCY_LEVEL && nextIndex < urls.Length)
{
imageTasks.Add(GetBitmapAsync(urls[nextIndex]));
nextIndex++;
}
while(imageTasks.Count > 0)
{
try
{
Task<Bitmap> imageTask = await Task.WhenAny(imageTasks);
imageTasks.Remove(imageTask);
Bitmap image = await imageTask;
panel.AddImage(image);
}
catch(Exception exc) { Log(exc); }
if (nextIndex < urls.Length)
{
imageTasks.Add(GetBitmapAsync(urls[nextIndex]));
nextIndex++;
}
}

Rescate temprano
Considere que se est esperando asincrnicamente que una operacin se complete mientras simultneamente se
responde a la solicitud de cancelacin de un usuario (por ejemplo, un usuario hace clic en el botn de cancelacin). En el
siguiente cdigo se muestra este escenario:
private CancellationTokenSource m_cts;

public void btnCancel_Click(object sender, EventArgs e)

MCT: Luis Dueas Pag 16 de 80


Modelos para la Programacin Asincrnica en .NET

{
if (m_cts != null) m_cts.Cancel();
}

public async void btnRun_Click(object sender, EventArgs e)


{
m_cts = new CancellationTokenSource();
btnRun.Enabled = false;
try
{
Task<Bitmap> imageDownload = GetBitmapAsync(txtUrl.Text);
await UntilCompletionOrCancellation(imageDownload, m_cts.Token);
if (imageDownload.IsCompleted)
{
Bitmap image = await imageDownload;
panel.AddImage(image);
}
else imageDownload.ContinueWith(t => Log(t));
}
finally { btnRun.Enabled = true; }
}

private static async Task UntilCompletionOrCancellation(


Task asyncOp, CancellationToken ct)
{
var tcs = new TaskCompletionSource<bool>();
using(ct.Register(() => tcs.TrySetResult(true)))
await Task.WhenAny(asyncOp, tcs.Task);
return asyncOp;
}
Esta implementacin vuelve a habilitar la interfaz de usuario en cuanto decidamos salir pero no cancela las operaciones
asincrnicas subyacentes. Otra alternativa sera cancelar las operaciones pendientes cuando decidamos salir, aunque no
se restaure la interfaz de usuario hasta que las operaciones se completen efectivamente, posiblemente por una
finalizacin temprana debido a una solicitud de cancelacin:
private CancellationTokenSource m_cts;

public async void btnRun_Click(object sender, EventArgs e)


{
m_cts = new CancellationTokenSource();
btnRun.Enabled = false;
try
{
Task<Bitmap> imageDownload = GetBitmapAsync(txtUrl.Text, m_cts.Token);
await UntilCompletionOrCancellation(imageDownload, m_cts.Token);
Bitmap image = await imageDownload;
panel.AddImage(image);
}
catch(OperationCanceledException) {}
finally { btnRun.Enabled = true; }
}
Otro ejemplo de rescate temprano implica usar el mtodo WhenAny junto con el mtodo Delay como se discute en la
siguiente seccin.

Task.Delay
Se puede usar el mtodo Delay para introducir pausas en una ejecucin asincrnica del mtodo. Esto es til para
muchos tipos de funcionalidad, como compilar los bucles de sondeo y retrasar el control de datos proporcionados por
el usuario durante un perodo de tiempo predeterminado. El mtodo Delay puede tambin resultar til junto con
WhenAny para implementar tiempos de espera.
Si una tarea que forma parte de una operacin asincrnica mayor (por ejemplo, un servicio web ASP.NET) tarda
demasiado tiempo en completarse, la operacin global podra sufrir, especialmente si no llega a completarse nunca. Por
esta razn, es importante poder tener tiempos muertos cuando se espera en una operacin asincrnica.Los mtodos
sincrnicos Wait, WaitAll y WaitAny aceptan valores de tiempo de espera, pero los mtodos ContinueWhenAll/
WhenAny y el mencionado previamente WhenAll/WhenAny correspondientes no. En su lugar, se puede usar Delay
y WhenAny en conjunto para implementar un tiempo de espera.

MCT: Luis Dueas Pag 17 de 80


Modelos para la Programacin Asincrnica en .NET

Por ejemplo, en la aplicacin de interfaz de usuario, digamos que se desea descargar una imagen y deshabilitar la
interfaz de usuario mientras se descarga dicha imagen.Sin embargo, si la descarga lleva mucho tiempo, se desea
rehabilitar la interfaz de usuario y la descarga debe descartarse:
public async void btnDownload_Click(object sender, EventArgs e)
{
btnDownload.Enabled = false;
try
{
Task<Bitmap> download = GetBitmapAsync(url);
if (download == await Task.WhenAny(download, Task.Delay(3000)))
{
Bitmap bmp = await download;
pictureBox.Image = bmp;
status.Text = Downloaded;
}
else
{
pictureBox.Image = null;
status.Text = Timed out;
var ignored = download.ContinueWith(
t => Trace(Task finally completed));
}
}
finally { btnDownload.Enabled = true; }
}
Lo mismo ocurre con las descargas mltiples, puesto que WhenAll devuelve una tarea:
public async void btnDownload_Click(object sender, RoutedEventArgs e)
{
btnDownload.Enabled = false;
try
{
Task<Bitmap[]> downloads =
Task.WhenAll(from url in urls select GetBitmapAsync(url));
if (downloads == await Task.WhenAny(downloads, Task.Delay(3000)))
{
foreach(var bmp in downloads) panel.AddImage(bmp);
status.Text = Downloaded;
}
else
{
status.Text = Timed out;
downloads.ContinueWith(t => Log(t));
}
}
finally { btnDownload.Enabled = true; }
}

Compilar elementos de combinacin basados en tareas


Dado que una tarea es capaz de representar completamente una operacin asincrnica y proporcionar funciones
sincrnicas y asincrnicas para combinar con la operacin, recuperar los resultados, etc., es posible compilar las
bibliotecas tiles de los combinadores que constituyen las tareas para compilar modelos ms grandes. Como se ha visto
en la seccin anterior, .NET Framework incluye varios combinadores integrados, pero tambin se pueden compilar los
propios. Las siguientes secciones ofrecen varios ejemplos de posibles mtodos de elementos de combinacin.

RetryOnFault
En muchas situaciones, se puede desear reintentar una operacin si un intento previo da error. Para el cdigo sincrnico,
se podra compilar un mtodo auxiliar como RetryOnFault en el siguiente ejemplo para lograrlo:
public static T RetryOnFault<T>(Func<T> function, int maxTries)
{
for(int i=0; i<maxTries; i++)
{
try { return function(); }
catch { if (i == maxTries-1) throw; }
}

MCT: Luis Dueas Pag 18 de 80


Modelos para la Programacin Asincrnica en .NET

return default(T);
}
Se puede compilar un mtodo auxiliar casi idntico para las operaciones asincrnicas implementadas con el TAP y que
devuelvan tareas:
public static async Task<T> RetryOnFault<T>(Func<Task<T>> function, int
maxTries)
{
for(int i=0; i<maxTries; i++)
{
try { return await function().ConfigureAwait(false); }
catch { if (i == maxTries-1) throw; }
}
return default(T);
}
Se puede usar este combinador para encapsular reintentos en la lgica de la aplicacin; por ejemplo:
// Download the URL, trying up to three times in case of failure
string pageContents = await RetryOnFault(() => DownloadStringAsync(url), 3);
Se podra extender la funcin RetryOnFault ms adelante: Por ejemplo, la funcin podra aceptar otro Func<Task> que
se invocar entre reintentos para determinar cundo probar la operacin otra vez; por ejemplo:
public static async Task<T> RetryOnFault<T>(
Func<Task<T>> function, int maxTries, Func<Task> retryWhen)
{
for(int i=0; i<maxTries; i++)
{
try { return await function().ConfigureAwait(false); }
catch { if (i == maxTries-1) throw; }
await retryWhen().ConfigureAwait(false);
}
return default(T);
}
Se podra usar la funcin como se muestra para esperar un segundo antes de reintentar la operacin:
// Download the URL, trying up to three times in case of failure,
// and delaying for a second between retries
string pageContents = await RetryOnFault(
() => DownloadStringAsync(url), 3, () => Task.Delay(1000));

NeedOnlyOne
A veces, se puede aprovechar la redundancia para mejorar la latencia y las posibilidades de xito de una
operacin. Piense en los mltiples servicios web que proporcionan cotizaciones, pero que, durante varias horas del da
cada uno de los servicios puede proporcionar diferentes niveles de calidad y de tiempos de respuesta. Para tratar con
estas fluctuaciones, se pueden mandar solicitudes a todos los servicios web y tan pronto como se reciba una respuesta
de una, cancelar las solicitudes restantes. Se puede implementar una funcin auxiliar para facilitar la implementacin de
este patrn comn para iniciar mltiples operaciones, esperar alguna y despus de cancelar el resto: La
funcin NeedOnlyOne en el siguiente ejemplo muestra este escenario:
public static async Task<T> NeedOnlyOne(
params Func<CancellationToken,Task<T>> [] functions)
{
var cts = new CancellationTokenSource();
var tasks = (from function in functions
select function(cts.Token)).ToArray();
var completed = await Task.WhenAny(tasks).ConfigureAwait(false);
cts.Cancel();
foreach(var task in tasks)
{
var ignored = task.ContinueWith(
t => Log(t), TaskContinuationOptions.OnlyOnFaulted);
}
return completed;
}
Esta funcin se puede utilizar como se indica a continuacin:

MCT: Luis Dueas Pag 19 de 80


Modelos para la Programacin Asincrnica en .NET

double currentPrice = await NeedOnlyOne(


ct => GetCurrentPriceFromServer1Async(msft, ct),
ct => GetCurrentPriceFromServer2Async(msft, ct),
ct => GetCurrentPriceFromServer3Async(msft, ct));

Operaciones de intercalado
Existe un potencial problema de rendimiento si se usa el mtodo WhenAny para admitir un escenario de intercalado
cuando se utilizan conjuntos de tareas muy grandes.Cada llamada a WhenAny da como resultado una continuacin que
se registra con cada tarea. Para un nmero N de tareas, esto da como resultado O(N2) continuaciones creadas sobre el
tiempo de vida de la operacin de intercalado. Si se est trabajando con un gran nmero de tareas, se puede usar un
combinador (Interleaved en el siguiente ejemplo) para solucionar el problema de rendimiento:
static IEnumerable<Task<T>> Interleaved<T>(IEnumerable<Task<T>> tasks)
{
var inputTasks = tasks.ToList();
var sources = (from _ in Enumerable.Range(0, inputTasks.Count)
select new TaskCompletionSource<T>()).ToList();
int nextTaskIndex = -1;
foreach (var inputTask in inputTasks)
{
inputTask.ContinueWith(completed =>
{
var source = sources[Interlocked.Increment(ref nextTaskIndex)];
if (completed.IsFaulted)
source.TrySetException(completed.Exception.InnerExceptions);
else if (completed.IsCanceled)
source.TrySetCanceled();
else
source.TrySetResult(completed.Result);
}, CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
}
return from source in sources select source.Task;
}
Se podra utilizar el combinador para procesar los resultados de tareas a medida que stas se completan; por ejemplo:
IEnumerable<Task<int>> tasks = ...;
foreach(var task in Interleaved(tasks))
{
int result = await task;

}

WhenAllOrFirstException
En ciertos escenarios de dispersin/recoleccin, quizs se desee esperar a todas las tareas de un conjunto, a menos que
una de ellas d error, en cuyo caso se desee detener la espera tan pronto se produzca la excepcin. Se puede conseguir
esto con un mtodo combinador como WhenAllOrFirstException en el siguiente ejemplo:
public static Task<T[]> WhenAllOrFirstException<T>(IEnumerable<Task<T>> tasks)
{
var inputs = tasks.ToList();
var ce = new CountdownEvent(inputs.Count);
var tcs = new TaskCompletionSource<T[]>();
Action<Task> onCompleted = (Task completed) =>
{
if (completed.IsFaulted)
tcs.TrySetException(completed.Exception.InnerExceptions);
if (ce.Signal() && !tcs.Task.IsCompleted)
tcs.TrySetResult(inputs.Select(t => t.Result).ToArray());
};
foreach (var t in inputs) t.ContinueWith(onCompleted);
return tcs.Task;
}

Compilando estructuras de datos basadas en tareas

MCT: Luis Dueas Pag 20 de 80


Modelos para la Programacin Asincrnica en .NET

Adems de la capacidad de compilar elementos de combinacin basados en tareas personalizadas, el hecho de tener
una estructura de datos en Task y Task<TResult> que representa tanto los resultados de una operacin asincrnica y la
sincronizacin necesaria para unirlo hace que sea un tipo muy eficaz para compilar las estructuras de datos
personalizadas que se utilizarn en escenarios asincrnicos.

AsyncCache
Un aspecto importante de una tarea es que se puede distribuir a todos los consumidores que la esperan, que registran
continuaciones con ella, que obtienen su resultado o excepciones (en el caso de Task<TResult>), etc. Esto hace
que Task y Task<TResult> sean perfectamente aptos para su uso en una infraestructura de almacenamiento en cach
asincrnica. He aqu un ejemplo de una pequea pero eficaz memoria cach asincrnica compilada sobre Task
<TResult>:
public class AsyncCache<TKey, TValue>
{
private readonly Func<TKey, Task<TValue>> _valueFactory;
private readonly ConcurrentDictionary<TKey, Lazy<Task<TValue>>> _map;

public AsyncCache(Func<TKey, Task<TValue>> valueFactory)


{
if (valueFactory == null) throw new ArgumentNullException("loader");
_valueFactory = valueFactory;
_map = new ConcurrentDictionary<TKey, Lazy<Task<TValue>>>();
}

public Task<TValue> this[TKey key]


{
get
{
if (key == null) throw new ArgumentNullException("key");
return _map.GetOrAdd(key, toAdd =>
new Lazy<Task<TValue>>(() => _valueFactory(toAdd))).Value;
}
}
}
La clase AsyncCache<TKey,TValue> acepta como delegado de su constructor una funcin que toma un TKey y devuelve
un Task<TResult>. Cualquier valor al que se ha obtenido acceso previamente desde la memoria cach se almacena en el
diccionario interno y AsyncCache asegura que slo una tarea se genera por clave, aun cuando se obtenga acceso a la
memoria cach simultneamente.
Por ejemplo, se puede construir una memoria cach para pginas web descargadas:
private AsyncCache<string,string> m_webPages =
new AsyncCache<string,string>(DownloadStringAsync);
Se puede usar esta memoria cach en mtodos asincrnicos siempre que se necesiten los contenidos de una pgina
web. La clase AsyncCache garantiza que se estn descargando tan pocas pginas como sea posible y guarda en
memoria cach los resultados.
private async void btnDownload_Click(object sender, RoutedEventArgs e)
{
btnDownload.IsEnabled = false;
try
{
txtContents.Text = await m_webPages["http://www.microsoft.com"];
}
finally { btnDownload.IsEnabled = true; }
}

AsyncProducerConsumerCollection
Tambin se pueden utilizar tareas para compilar las estructuras de datos con el fin de coordinar las actividades
asincrnicas entre s. Piense en uno de los patrones paralelos clsicos de diseo: productor/consumidor. En este patrn,
los productores generan datos que los consumidores adquieren y los productores y consumidores pueden ejecutarlos
en paralelo. Por ejemplo, el consumidor procesa el elemento 1, que ha sido generado previamente por un productor
que est ahora produciendo el elemento 2. Para el patrn productor/consumidor, siempre se necesita alguna estructura
de datos para almacenar el trabajo creado por los productores de forma que se puedan notificar los nuevos datos a los
consumidores y stos puedan encontrarlos cundo estn disponibles.
A continuacin se muestra una simple estructura de datos compilada sobre una tarea que habilita los mtodos
asincrnicos que se utilizarn como productores y consumidores:

MCT: Luis Dueas Pag 21 de 80


Modelos para la Programacin Asincrnica en .NET

public class AsyncProducerConsumerCollection<T>


{
private readonly Queue<T> m_collection = new Queue<T>();
private readonly Queue<TaskCompletionSource<T>> m_waiting =
new Queue<TaskCompletionSource<T>>();

public void Add(T item)


{
TaskCompletionSource<T> tcs = null;
lock (m_collection)
{
if (m_waiting.Count > 0) tcs = m_waiting.Dequeue();
else m_collection.Enqueue(item);
}
if (tcs != null) tcs.TrySetResult(item);
}

public Task<T> Take()


{
lock (m_collection)
{
if (m_collection.Count > 0)
{
return Task.FromResult(m_collection.Dequeue());
}
else
{
var tcs = new TaskCompletionSource<T>();
m_waiting.Enqueue(tcs);
return tcs.Task;
}
}
}
}
Con esa estructura de datos en su lugar, se puede escribir cdigo como el siguiente:
private static AsyncProducerConsumerCollection<int> m_data = ;

private static async Task ConsumerAsync()


{
while(true)
{
int nextItem = await m_data.Take();
ProcessNextItem(nextItem);
}
}

private static void Produce(int data)


{
m_data.Add(data);
}
El espacio de nombres System.Threading.Tasks.Dataflow incluye el tipo BufferBlock<T>, que se puede usar de una
manera similar, pero sin tener que construir un tipo de colecciones personalizado:
private static BufferBlock<int> m_data = ;

private static async Task ConsumerAsync()


{
while(true)
{
int nextItem = await m_data.ReceiveAsync();
ProcessNextItem(nextItem);
}
}

private static void Produce(int data)


{

MCT: Luis Dueas Pag 22 de 80


Modelos para la Programacin Asincrnica en .NET

m_data.Post(data);
}

Nota

El espacio de nombres System.Threading.Tasks.Dataflow est disponible en el .NET Framework 4.5 por medio
de NuGet. Para instalar el ensamblado que contiene el espacio de nombres System.Threading.Tasks.Dataflow, abra su
proyecto en Visual Studio 2012, elija Administrar paquetes NuGet desde el men del proyecto y busque en lnea el
paquete Microsoft.Tpl.Dataflow.

1.3. Interoperabilidad con otros tipos y patrones asincrnicos


.NET Framework 1.0 present el modelo IAsyncResult, conocido tambin como Modelo de programacin asincrnica
(APM), o el modelo de Begin/End. .NET Framework 2.0 agreg Patrn asincrnico basado en eventos (EAP). A partir de
.NET Framework 4, Modelo asincrnico basado en tareas (TAP) reemplaza a APM y EAP, pero proporciona la capacidad
de compilar fcilmente rutinas de migracin de los modelos anteriores:
Tareas y APM (de APM a TAP o de TAP a APM)
Tareas y EAP
Tareas y controladores de espera (de identificadores de espera a TAP o de TAP a identificadores de espera)

Tareas y el modelo de programacin asincrnica (APM)


De APM a TAP
Como el modelo Modelo de programacin asincrnica (APM) es muy estructurado, es muy fcil compilar un contenedor
para exponer una implementacin de APM como una implementacin de TAP. De hecho, .NET Framework 4 incluye
rutinas auxiliares en forma de sobrecargas del mtodo FromAsync para proporcionar esta traduccin.
Considere la clase Stream y sus mtodos BeginRead/EndRead, que representan el equivalente de APM al mtodo
sincrnico Read:
public int Read(byte [] buffer, int offset, int count);

public IAsyncResult BeginRead(byte [] buffer, int offset, int count,


AsyncCallback callback, object state);
public int EndRead(IAsyncResult asyncResult);
Puede usar el mtodo FromAsync para implementar un contenedor de TAP para este mtodo como sigue:
public static Task<int> ReadAsync(
this Stream stream, byte [] buffer, int offset, int count)
{
if (stream == null) throw new ArgumentNullException(stream);
return Task<int>.Factory.FromAsync(stream.BeginRead, stream.EndRead,
buffer, offset, count, null);
}
Esta implementacin es similar a la siguiente:
public static Task<int> ReadAsync(
this Stream stream, byte [] buffer, int offset, int count)
{
if (stream == null) throw new ArgumentNullException("stream");
var tcs = new TaskCompletionSource<int>();
stream.BeginRead(buffer, offset, count, iar =>
{
try { tcs.TrySetResult(stream.EndRead(iar)); }
catch(OperationCanceledException) { tcs.TrySetCanceled(); }
catch(Exception exc) { tcs.TrySetException(exc); }
}, null);
return tcs.Task;
}

De TAP a APM
Si la infraestructura existente espera el modelo APM, tambin desear realizar una implementacin de TAP y usarla
donde se espere una implementacin de APM. Dado que las tareas se pueden componer y la
clase Task implementa IAsyncResult, puede utilizar una funcin directa auxiliares para ello. En el cdigo siguiente se usa
una extensin de la clase Task<TResult>, pero puede usar una funcin casi idntica para las tareas no genricas.
public static IAsyncResult AsApm<T>(

MCT: Luis Dueas Pag 23 de 80


Modelos para la Programacin Asincrnica en .NET

this Task<T> task, AsyncCallback callback, object state)


{
if (task == null) throw new ArgumentNullException(task);
var tcs = new TaskCompletionSource<T>(state);
task.ContinueWith(t =>
{
if (t.IsFaulted) tcs.TrySetException(t.Exception.InnerExceptions)
else if (t.IsCanceled) tcs.TrySetCanceled();
else tcs.TrySetResult(t.Result);

if (callback != null) callback(tcs.Task);


}, TaskScheduler.Default);
return tcs.Task;
}
Ahora, considere un caso donde tiene la implementacin siguiente de TAP:
public static Task<string> DownloadStringAsync(Uri url);
y desea proporcionar esta implementacin de APM:
public IAsyncResult BeginDownloadString(
Uri url, AsyncCallback callback, object state);
public string EndDownloadString(IAsyncResult asyncResult);
En el cdigo siguiente se muestra una migracin a APM:
public IAsyncResult BeginDownloadString(
Uri url, AsyncCallback callback, object state)
{
return DownloadStringAsync(url).AsApm(callback, state);
}

public string EndDownloadString(IAsyncResult asyncResult)


{
return ((Task<string>)asyncResult).Result;
}

Tareas y el modelo asincrnico basado en eventos (EAP)


Ajustar una implementacin de Patrn asincrnico basado en eventos (EAP) es ms complejo que ajustar un modelo de
APM, ya que el modelo de EAP tiene ms variacin y menos estructura que el modelo de APM. Para mostrar, el cdigo
siguiente contiene el mtodo DownloadStringAsync. DownloadStringAsync acepta el URI, provoca el evento
DownloadProgressChanged mientras descarga para sealar estadsticas varios sobre el progreso, y provoca el
evento DownloadStringCompleted cuando ha hecho. El resultado final es una cadena que incluye el contenido de la
pgina en el URI especificado.
public static Task<string> DownloadStringAsync(Uri url)
{
var tcs = new TaskCompletionSource<string>();
var wc = new WebClient();
wc.DownloadStringCompleted += (s,e) =>
{
if (e.Error != null) tcs.TrySetException(e.Error);
else if (e.Cancelled) tcs.TrySetCanceled();
else tcs.TrySetResult(e.Result);
};
wc.DownloadStringAsync(url);
return tcs.Task;
}

Tareas e identificadores de espera


De identificadores de espera a TAP
Aunque los identificadores de espera no implementan un modelo asincrnico, los desarrolladores avanzados pueden
usar la clase WaitHandle y el mtodoThreadPool.RegisterWaitForSingleObject para las notificaciones asincrnicas
cuando se establece un identificador de espera. Puede ajustar RegisterWaitForSingleObjectpara habilitar una alternativa
basada en tareas en cualquier espera sincrnica en un identificador de espera:
public static Task WaitOneAsync(this WaitHandle waitHandle)
{

MCT: Luis Dueas Pag 24 de 80


Modelos para la Programacin Asincrnica en .NET

if (waitHandle == null) throw new ArgumentNullException("waitHandle");


var tcs = new TaskCompletionSource<bool>();
var rwh = ThreadPool.RegisterWaitForSingleObject(waitHandle,
delegate { tcs.TrySetResult(true); }, null, -1, true);
var t = tcs.Task;
t.ContinueWith(_ => rwh.Unregister(null));
return t;
}
Con este mtodo se pueden usar las implementaciones existentes WaitHandle en mtodos asincrnicos. Por ejemplo, si
desea restringir el nmero de operaciones asincrnicas que se ejecutan en un momento dado, puede usar un semforo
(objeto System.Threading.Semaphore). Se puede restringir a N el nmero de operaciones que se ejecutan en paralelo,
inicializando el recuento del semforo a N, esperando al semforo cuando se desee realizar una operacin y liberando el
semforo cuando se haya terminado:
static Semaphore m_throttle = new Semaphore(N, N);
static async Task DoOperation()
{
await m_throttle.WaitOneAsync();
// do work
m_throttle.ReleaseOne();
}
Tambin se puede compilar un semforo asincrnico que no se base en identificadores de espera, sino que funcione
completamente con tareas. Para ello, puede usar tcnicas como las descritas en Utilizar el modelo asincrnico basado en
tareas para compilar estructuras de datos sobre Task. De hecho, el tipo SemaphoreSlim expone un mtodo Wait
Async que habilita esta funcionalidad.

De TAP a identificadores de espera


Como se mencion anteriormente, la clase Task implementa IAsyncResult y expone una propiedad IAsyncResult.
AsyncWaitHandle que devuelve un identificador de espera establecido cuando la Task ha sido completada. Puede
obtener la instancia de WaitHandle para una Task de esta manera:
WaitHandle wh = ((IAsyncResult)task).AsyncWaitHandle;

MCT: Luis Dueas Pag 25 de 80


Modelos para la Programacin Asincrnica en .NET

2. Patrn asincrnico basado en eventos (EAP)


Hay varias maneras de exponer las caractersticas asincrnicas al cdigo de cliente. El modelo asincrnico basado en
eventos prescribe una manera recomendada para que las clases presenten comportamiento asincrnico.

Nota

A partir de .NET Framework 4, la biblioteca TPL (Task Parallel Library, biblioteca de procesamiento paralelo
basado en tareas) proporciona un nuevo modelo para programacin asincrnica y paralela.

2.1. Programacin multiproceso con el modelo asincrnico


basado en eventos
Hay varias maneras de exponer las caractersticas asincrnicas al cdigo de cliente. El Modelo asincrnico basado en
evento prescribe la manera recomendada para que las clases presenten comportamiento asincrnico.

2.1.1. Informacin general sobre el modelo asincrnico basado


en eventos
Las aplicaciones que realizan simultneamente muchas tareas y que, aun as, siguen siendo receptivas a la interaccin
con el usuario, a menudo requieren un diseo que utiliza varios subprocesos. El espacio de
nombres System.Threading proporciona todas las herramientas necesarias para crear aplicaciones multiproceso de alto
rendimiento; pero el uso eficaz de dichas herramientas exige una amplia experiencia en ingeniera de software
multiproceso. Para las aplicaciones multiprocesos relativamente simples, el componente BackgroundWorker
proporciona una solucin sencilla. Para aplicaciones asincrnicas ms sofisticadas, pruebe a implementar una clase que
siga el modelo asincrnico basado en eventos.
El modelo asincrnico basado en eventos pone a disposicin del usuario las ventajas de las aplicaciones multiproceso, al
tiempo que evita muchos de los problemas complejos inherentes al diseo multiproceso. Utilizar una clase que admita
este modelo puede permitirle:
Realizar tareas que exigen mucho tiempo (como descargas y operaciones con bases de datos) "en segundo
plano", sin interrumpir la aplicacin.
Ejecutar simultneamente varias operaciones y recibir la notificacin correspondiente cuando finalice cada una
de ellas.
Esperar a que estn disponibles los recursos sin que la aplicacin se detenga (no responda).
Establecer comunicacin con operaciones asincrnicas pendientes utilizando el modelo habitual de eventos y
delegados.
Toda clase que admita el modelo asincrnico basado en eventos tendr uno o varios mtodos
denominados nombreDeMtodoAsync. Estos mtodos pueden reflejar versiones sincrnicas, que realizan la misma
operacin en el subproceso actual. La clase tambin puede tener un evento nombreDeMtodoCompleted y un
mtodonombreDeMtodoAsyncCancel (o simplemente CancelAsync).
PictureBox es un componente tpico que admite el modelo asincrnico basado en eventos. Puede descargar una imagen
de forma sincrnica llamando a su mtodo Load, pero si se trata de una imagen que ocupe mucho, o si tiene una
conexin de red lenta, la aplicacin se detendr ("no responder") hasta que finalice la operacin de descarga y se
devuelva la llamada a Load.
Si desea que la aplicacin se siga ejecutando mientras se carga la imagen, puede llamar al mtodo LoadAsync y
controlar el evento LoadCompleted, exactamente igual que controlara cualquier otro evento. Cuando llame al
mtodo LoadAsync, la aplicacin continuar ejecutndose mientras se produce la descarga en un subproceso
independiente ("en segundo plano"). Una vez finalizada la carga de la imagen, se llamar al controlador de eventos, que
examinar el parmetro AsyncCompletedEventArgspara determinar si la descarga ha finalizado correctamente.
El modelo asincrnico basado en eventos exige que se pueda cancelar una operacin asincrnica, y el
control PictureBox admite este requisito con su mtodo CancelAsync. Al llamar al mtodo CancelAsync, se enva una
solicitud para detener la descarga pendiente y, cuando se cancela la tarea, se provoca el evento LoadCompleted.

Precaucin

Es posible que la descarga finalice justo en el momento en que se realiza la solicitud CancelAsync, por lo
que Cancelled puede no reflejar la solicitud de cancelacin. Esto se denomina condicin de carrera y es un
problema comn en la programacin multiproceso.

Caractersticas del modelo asincrnico basado en eventos

MCT: Luis Dueas Pag 26 de 80


Modelos para la Programacin Asincrnica en .NET

El modelo asincrnico basado en eventos puede adoptar varias formas, dependiendo de la complejidad de las
operaciones admitidas por una clase determinada. Las clases ms simples pueden tener un nico
mtodo NombreMtodoAsync con su evento NombreMtodoCompleted correspondiente. Las clases ms complejas
pueden tener varios mtodos NombreMtodoAsync, cada uno de ellos con su evento NombreMtodo
Completed correspondiente, as como versiones sincrnicas de estos mtodos. Las clases pueden opcionalmente
admitir la cancelacin, la emisin de informes de progreso y la generacin de resultados incrementales para cada
mtodo asincrnico.
Todo mtodo asincrnico puede tambin admitir varias llamadas pendientes (varias invocaciones simultneas), lo que
permite que el cdigo lo llame cualquier nmero de veces antes de finalizar otras operaciones pendientes. Para
controlar correctamente esta situacin, puede que la aplicacin deba realizar un seguimiento de la finalizacin de cada
operacin.

Ejemplos del modelo asincrnico basado en eventos


Los componentes SoundPlayer y PictureBox representan implementaciones sencillas del modelo asincrnico basado en
eventos. Los componentes WebClient y BackgroundWorker representan implementaciones ms complejas del modelo
asincrnico basado en eventos.
A continuacin se muestra un ejemplo de declaracin de clase que se ajusta al modelo:
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 ficticia AsyncExample tiene dos mtodos y ambos admiten invocaciones sincrnicas y asincrnicas. Las
sobrecargas sincrnicas se comportan como cualquier llamada a un mtodo y ejecutan la operacin en el subproceso
que realiza la llamada. Si esta operacin llevase mucho tiempo, podra haber un retraso significativo en la devolucin de
la llamada. Las sobrecargas asincrnicas inician la operacin en otro subproceso y despus regresan inmediatamente, lo
que permite que el subproceso que realiza la llamada contine mientras la operacin se ejecuta "en segundo plano".

Sobrecargas de mtodo asincrnicas


Hay dos sobrecargas posibles para las operaciones asincrnicas: de invocacin nica y de invocacin mltiple. Las dos
formas se distinguen por sus signaturas de mtodo: la sobrecarga de invocacin mltiple tiene un parmetro adicional
denominado userState. Esto permite que el cdigo llame varias veces al mtodo Method1Async(string param,
object userState), sin esperar a que finalice ninguna operacin asincrnica pendiente. Si, por otro lado, intenta
llamar al mtodo Method1Async(string param) antes de que haya finalizado una invocacin anterior, el mtodo
producir una excepcin InvalidOperationException.
El parmetro userState para las sobrecargas de invocacin mltiple le permite distinguir entre operaciones
asincrnicas. Debe proporcionar un valor nico, como un identificador nico global (GUID) o cdigo hash, para cada
llamada al mtodo Method1Async(string param, object userState) y, una vez finalizada cada operacin,
el controlador de eventos podr determinar qu instancia de la operacin provoc el evento de finalizacin.

Realizar el seguimiento de las operaciones pendientes


Si utiliza las sobrecargas de invocacin mltiple, el cdigo necesitar realizar un seguimiento de los
objetos userState (identificadores de tarea) para las tareas pendientes.Para cada llamada al
mtodo Method1Async(string param, object userState), normalmente generar un nuevo y nico objeto
userState y lo agregar a una coleccin. Cuando la tarea correspondiente a este objeto userState provoque el evento de
finalizacin, la implementacin del mtodo de finalizacin examinar AsyncCompletedEventArgs.UserState, que despus
se quitar de la coleccin. Utilizado de esta manera, el parmetro userState asume el rol de un identificador de tarea.

Nota

Debe tener cuidado de proporcionar un valor nico para userState en las llamadas a sobrecargas de invocacin
mltiple. Los identificadores de tarea con un valor no nico harn que la clase asincrnica produzca una
excepcin ArgumentException.

MCT: Luis Dueas Pag 27 de 80


Modelos para la Programacin Asincrnica en .NET

Cancelar las operaciones pendientes


Es importante poder cancelar las operaciones asincrnicas en cualquier momento antes de su finalizacin. Las clases que
implementen el modelo asincrnico basado en eventos tendrn un mtodo CancelAsync (si solo hay un mtodo
asincrnico) o un mtodo nombreDeMtodoAsyncCancel (si hay varios mtodos asincrnicos).
Los mtodos que permiten varias invocaciones toman un parmetro userState, que se puede usar para realizar un
seguimiento de la duracin de cada tarea. CancelAsynctoma un parmetro userState, que le permite cancelar
determinadas tareas pendientes.
Los mtodos que admiten slo una operacin pendiente cada vez, como Method1Async(string param), no son
cancelables.

Recibir actualizaciones del progreso y resultados incrementales


Toda clase que se ajuste al modelo asincrnico basado en eventos puede proporcionar opcionalmente un evento para
realizar el seguimiento del progreso y los resultados incrementales. Dicho evento se suele denominar Progress
Changed o nombreDeMtodoProgressChanged y su controlador de eventos correspondiente tomar un parmetro
ProgressChangedEventArgs.
El controlador de eventos para el evento ProgressChanged puede examinar la propiedad ProgressChangedEventArgs.
ProgressPercentage para determinar qu porcentaje de una tarea asincrnica ha finalizado. Esta propiedad oscilar entre
0 y 100 y se puede utilizar para actualizar la propiedad Value de un objeto ProgressBar. Si hay varias operaciones
asincrnicas pendientes, puede utilizar la propiedad ProgressChangedEvent Args.UserState para discernir qu operacin
est dando informacin de progreso.
Algunas clases pueden emitir un informe con resultados incrementales durante el progreso de las operaciones
asincrnicas. Estos resultados se almacenan en una clase que deriva de ProgressChangedEventArgs y aparecen como
propiedades en la clase derivada. Se puede obtener acceso a dichos resultados en el controlador de eventos para el
evento ProgressChanged, de la misma forma que se obtiene acceso a la propiedad ProgressPercentage. Si hay varias
operaciones asincrnicas pendientes, puede utilizar la propiedad UserState para discernir qu operacin est dando
informacin sobre resultados incrementales.

2.1.2. Implementar el modelo asincrnico basado en eventos


Si est escribiendo una clase con algunas operaciones que puedan incurrir en retrasos notables, puede probar a darle
funcionalidad asincrnica implementando Informacin general sobre el modelo asincrnico basado en eventos.
El modelo asincrnico basado en eventos constituye una forma estandarizada de empaquetar clases con caractersticas
asincrnicas. Si se implementa con clases de ayuda como AsyncOperationManager, la clase funcionar correctamente
bajo cualquier modelo de aplicacin, incluidas ASP.NET, aplicaciones de consola y aplicaciones de Windows Forms.
Para las operaciones asincrnicas simples, el componente BackgroundWorker puede resultar apropiado.
En la lista siguiente se describen las caractersticas del modelo asincrnico basado en eventos que se tratan en este
tema.
Posibilidad de implementar el modelo asincrnico basado en eventos
Asignacin de nombres a los mtodos asincrnicos
Admisin opcional de la cancelacin
Admisin opcional de la propiedad IsBusy
Compatibilidad opcional con la informacin de progreso
Compatibilidad opcional con la devolucin de resultados incrementales
Uso de los parmetros Out y Ref en mtodos

Posibilidad de implementar el modelo asincrnico basado en eventos


Considere la posibilidad de implementar el modelo asincrnico basado en eventos en los siguientes casos:
Cuando los clientes de una clase no necesiten objetos WaitHandle y IAsyncResult para operaciones
asincrnicas, lo que significa que el sondeo y WaitAll o WaitAny tendrn que ser compilados por el cliente.
Cuando desee que las operaciones asincrnicas sean administradas por el cliente con el modelo conocido de
evento/delegado.
Cualquier operacin puede ser objeto de una implementacin asincrnica, si bien deberan considerarse de manera
especial aqullas cuya latencia pudiera ser ms larga.Son especialmentes adecuadas las operaciones en las que los
clientes llaman a un mtodo y se les enva una notificacin cuando finaliza, sin que sea necesaria ninguna otra
intervencin. Tambin son adecuadas las operaciones que se ejecutan de forma continua, y que avisan peridicamente a
los clientes sobre el progreso, los resultados incrementales o los cambios de estado.

Asignacin de nombres a los mtodos asincrnicos


Para cada mtodo nombreDeMtodo sincrnico para el que quiera proporcionar un homlogo asincrnico:
Defina un mtodo nombreDeMtodoAsync que:
Devuelva void.
Tome los mismos parmetros que el mtodo nombreDeMtodo.

MCT: Luis Dueas Pag 28 de 80


Modelos para la Programacin Asincrnica en .NET

Acepte varias invocaciones.


Si lo desea, tambin puede definir una sobrecarga nombreDeMtodoAsync, idntica a nombreDeMtodoAsync, pero con
un parmetro de valor de objeto adicional denominado userState. Haga esto si est preparado para administrar varias
invocaciones simultneas de su mtodo, en cuyo caso se devolver el valor userState a todos los controladores de
eventos para diferenciar las invocaciones del mtodo. Tambin puede hacerlo simplemente para crear un lugar donde
almacenar el estado del usuario para su posterior recuperacin.
Para cada signatura del mtodo nombreDeMtodoAsync independiente:

1. Defina el evento siguiente en la misma clase que el mtodo:


public event MethodNameCompletedEventHandler MethodNameCompleted;
2. Defina el delegado siguiente y AsyncCompletedEventArgs. stos probablemente se definirn fuera de la
propia clase, pero en el mismo espacio de nombres.
public delegate void MethodNameCompletedEventHandler(object sender,
MethodNameCompletedEventArgs e);

public class MethodNameCompletedEventArgs :


System.ComponentModel.AsyncCompletedEventArgs
{
public MyReturnType Result { get; }
}
o Asegrese de que la clase nombreDeMtodoCompletedEventArgs expone sus miembros como
propiedades de solo lectura, y no como campos, ya que los campos impiden el enlace de datos.
o No defina ninguna clase derivada de AsyncCompletedEventArgs para mtodos que no generan
resultados. Simplemente utilice una instancia del propio AsyncCompletedEventArgs.

Nota

Es perfectamente aceptable, siempre que sea factible y adecuado, reutilizar tipos Async
CompletedEventArgs y de delegado. En este caso, los nombres asignados no sern
necesariamente coherentes con el nombre del mtodo, ya que ni el delegado
ni AsyncCompletedEventArgs estarn asociados a un nico mtodo.

Admisin opcional de la cancelacin


Si una clase va a admitir la cancelacin de operaciones asincrnicas, dicha cancelacin se debera exponer al cliente tal y
como se describe a continuacin. Observe que deben resolverse dos cuestiones antes de definir si se admite la
cancelacin:
Tiene la clase, incluida cualquier futura adicin que se pueda anticipar, una nica operacin asincrnica que
admita la cancelacin?
Son compatibles las operaciones asincrnicas que admiten la cancelacin con la existencia de varias
operaciones pendientes? Es decir, puede tomar el mtodonombreDeMtodoAsync un parmetro 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 cul debera ser la signatura para el
mtodo de cancelacin.

Visual Basic
Admisin de varias operaciones
Slo una operacin cada vez
simultneas

Una operacin Sub MethodNameAsyncCancel(ByVal Sub


asincrnica en userState As Object) MethodNameAsyncCancel()
toda la clase

Varias Sub CancelAsync(ByVal userState Sub CancelAsync()


operaciones As Object)
asincrnicas en
la clase

C#
Admisin de varias operaciones
Slo una operacin cada vez
simultneas

MCT: Luis Dueas Pag 29 de 80


Modelos para la Programacin Asincrnica en .NET

Una operacin void void


asincrnica en MethodNameAsyncCancel(object MethodNameAsyncCancel();
toda la clase userState);

Varias void CancelAsync(object void CancelAsync();


operaciones userState);
asincrnicas en
la clase
Si define el mtodo CancelAsync(object userState), los clientes debern tener la precaucin, a la hora de
elegir sus valores de estado, de hacer que stos sean capaces de diferenciar entre todos los mtodos asincrnicos
invocados en el objeto, y no slo entre todas las invocaciones de un nico mtodo asincrnico.
La decisin de denominar a la versin de la operacin asincrnica nica como nombreDeMtodoAsyncCancel se basa en
la capacidad de detectar el mtodo con mayor facilidad en un entorno de diseo 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 asincrnica. Si espera que se puedan agregar operaciones asincrnicas adicionales en versiones
subsiguientes, es mejor definir CancelAsync.
No defina varios mtodos de la tabla anterior en la misma clase. Eso no tendr sentido, o recargar la interfaz de clase
con una proliferacin de mtodos.
Normalmente, estos mtodos devolvern un resultado inmediatamente y la operacin podra, o no, cancelarse
realmente. En el controlador de eventos del eventonombreDeMtodoCompleted, el objeto nombreDeMtodo
CompletedEventArgs contiene un campo Cancelled, que los clientes pueden utilizar para determinar si se ha
producido la cancelacin.

Admisin opcional de la propiedad IsBusy


Si una clase no admite varias invocaciones simultneas, considere la posibilidad de exponer una propiedad IsBusy. Esto
permite a los desarrolladores determinar si se est ejecutando un mtodo nombreDeMtodoAsync sin detectar una
excepcin del mtodo nombreDeMtodoAsync.
Atngase a la semntica de IsBusy descrita en Procedimientos recomendados para implementar el modelo asincrnico
basado en eventos.

Compatibilidad opcional con la informacin de progreso


Con frecuencia se desea que una operacin asincrnica informe sobre el progreso de su actividad. El modelo
asincrnico basado en eventos proporciona una pauta para ello.
Defina opcionalmente el evento que quiere que desencadene la operacin asincrnica 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 continuacin:
o ProgressChanged si la clase tiene varias operaciones asincrnicas (o si se espera que crezca y
llegue a incluir varias operaciones asincrnicas en versiones futuras);
o MethodName ProgressChanged si la clase tiene una sola operacin asincrnica.
Esta opcin de denominacin es paralela a la realizada para el mtodo de cancelacin, tal y como se describe
en la seccin Admisin opcional de la cancelacin.
Este evento debera utilizar la signatura del delegado ProgressChangedEventHandler y la
clase ProgressChangedEventArgs. Como alternativa, si se puede proporcionar otro indicador de progreso mas especfico
de dominio (por ejemplo, bytes ledos y bytes totales para una operacin de descarga), debera definir una clase
derivada de ProgressChangedEventArgs.
Observe que slo hay un evento ProgressChanged o nombreDeMtodoProgressChanged para la clase,
independientemente del nmero de mtodos asincrnicos que admita. Se espera que los clientes utilicen el
objeto userState que se pasa a los mtodos nombreDeMtodoAsync para distinguir entre las actualizaciones de progreso
que se producen en varias operaciones simultneas.
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 denominacin de nombreDeMtodo
ProgressChanged para cada mtodo nombreDeMtodoAsync.

Compatibilidad opcional con la devolucin de resultados incrementales


A veces, una operacin asincrnica puede devolver resultados incrementales antes de finalizar. Hay varias opciones que
se pueden utilizar para que admitan esta posibilidad. A continuacin, se dan algunos ejemplos.

MCT: Luis Dueas Pag 30 de 80


Modelos para la Programacin Asincrnica en .NET

Clase de operacin nica


Si una clase slo admite una operacin asincrnica nica y esa operacin puede devolver resultados incrementales,
entonces:
Ample el tipo ProgressChangedEventArgs para que lleve los datos del resultado incremental y defina un
evento nombreDeMtodoProgressChanged con estos datos extendidos.
Provoque este evento nombreDeMtodoProgressChanged cuando haya un resultado incremental sobre el
que informar.
Esta solucin es especfica de las clases de operaciones asincrnicas nicas, porque no hay ningn problema en que un
mismo evento devuelva resultados incrementales en "todas las operaciones", como sucede con el evento nombreDe
MtodoProgressChanged.

Clase de varias operaciones con resultados incrementales homogneos


En este caso, la clase admite varios mtodos asincrnicos, cada uno de ellos capaz de devolver resultados incrementales,
y estos resultados incrementales tienen todos los mismos tipos de datos.
Siga el modelo descrito ms arriba para las clases de operacin nica, ya que la estructura EventArgs es la misma para
todos los resultados incrementales. Defina un evento ProgressChanged en lugar de un evento nombreDeMtodo
ProgressChanged, ya que ste es vlido para varios mtodos asincrnicos.

Clase de varias operaciones con resultados incrementales heterogneos


Si la clase admite varios mtodos asincrnicos 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 nombreDeMtodoProgressChanged independiente con el tipo EventArgs adecuado para
que cada mtodo asincrnico pueda controlar los datos del resultado incremental de ese mtodo.

Uso de los parmetros Out y Ref en mtodos


Aunque en general no se recomienda el uso de out y ref en .NET Framework, he aqu las reglas que se deben seguir
cuando estn presentes:
Dado un mtodo sincrnico nombreDeMtodo:
Los parmetros out del mtodo nombreDeMtodo no deberan formar parte de nombreDeMtodoAsync. En su
lugar, deberan formar parte denombreDeMtodoCompletedEventArgs con el mismo nombre que su
parmetro equivalente en nombreDeMtodo (a menos que haya un nombre ms adecuado).
Los parmetros ref del mtodo nombreDeMtodo deberan aparecer como parte de nombreDeMtodoAsync, y
tambin como parte denombreDeMtodoCompletedEventArgs con el mismo nombre que su parmetro
equivalente en nombreDeMtodo (a menos que haya un nombre ms adecuado).
Por ejemplo, dado el cdigo:
public int MethodName(string arg1, ref string arg2, out string arg3);
El mtodo asincrnico y la clase AsyncCompletedEventArgs tendran el siguiente aspecto:
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; };
}

2.1.3. Procedimientos recomendados para implementar el


modelo asincrnico basado en eventos
El modelo asincrnico basado en eventos constituye una manera eficaz de exponer el comportamiento asincrnico en
clases, con semntica conocida de delegado y evento.Para implementar el modelo asincrnico 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 asincrnico basado en
eventos.
A continuacin, se muestra una lista con los procedimientos recomendados que se van a tratar en este tema:
Garantas de comportamiento obligatorias
Finalizacin
EventArgs y evento finalizado
Operaciones que se ejecutan simultneamente
Acceso a los resultados

MCT: Luis Dueas Pag 31 de 80


Modelos para la Programacin Asincrnica en .NET

Informacin de progreso
Implementacin de IsBusy
Cancelacin
Errores y excepciones
Subprocesamiento y contextos
Instrucciones

Garantas de comportamiento obligatorias


Cuando se implementa el modelo asincrnico basado en eventos, debe proporcionarse una serie de garantas que
aseguren que la clase se va a comportar adecuadamente y que los clientes de dicha clase pueden confiar en dicho
comportamiento.

Finalizacin
Invoque el controlador de eventos MethodNameCompleted siempre que la finalizacin sea correcta, o cuando se
produzca un error o una cancelacin. Nunca debera ocurrir que una aplicacin permaneciese inactiva sin llegar nunca a
la finalizacin. Como excepcin a esta regla, est el caso en que la propia operacin asincrnica est diseada para no
finalizar nunca.

EventArgs y evento finalizado


Para cada mtodo MethodNameAsync independiente, aplique los requisitos de diseo siguientes:
Defina un evento MethodNameCompleted en la misma clase que el mtodo.
Defina una clase EventArgs y el delegado que la acompaa para el evento MethodNameCompleted que
deriva de la clase AsyncCompletedEventArgs. El nombre de clase predeterminado debera adoptar la
forma MethodNameCompletedEventArgs.
Asegrese de que la clase EventArgs es especfica de los valores devueltos por el mtodo
MethodName. Cuando utilice la clase EventArgs, nunca debera exigirles a los desarrolladores que conviertan
el resultado.
En el ejemplo de cdigo siguiente se muestra, respectivamente, una implementacin buena y otra mala de
este requisito de diseo.
// 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 mtodos que devuelven void. En su lugar, use una instancia
de la clase AsyncCompletedEventArgs.
Asegrese de que se genere siempre el evento NombreMtodoCompleted. Este evento se debe generar
cuando se complete correctamente, cuando se produzca un error o cuando se cancele. Nunca debera ocurrir
que una aplicacin permaneciese inactiva sin llegar nunca a la finalizacin.
Asegrese de que se detecte cualquier excepcin que se produzca en la operacin asincrnica y se asigne la
excepcin 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, asegrese de que el acceso a cualquier propiedad de la
estructura EventArgs genere una excepcin. Utilice el mtodo RaiseExceptionIfNecessary para realizar la
comprobacin anterior.
Modele un tiempo de espera como un error. Cuando se produzca un tiempo de espera, genere el
evento NombreMtodoCompleted y asigne TimeoutException a la propiedad Error.
Si la clase admite varias invocaciones simultneas, asegrese de que el evento NombreMtodo
Completed contenga el objeto userSuppliedState apropiado.
Asegrese de que el evento NombreMtodoCompleted se genere en el subproceso adecuado y en el
momento oportuno del ciclo de vida de la aplicacin. Para obtener ms informacin, vea la seccin
Subprocesamiento y contextos.

MCT: Luis Dueas Pag 32 de 80


Modelos para la Programacin Asincrnica en .NET

Operaciones que se ejecutan simultneamente


Si la clase admite varias invocaciones simultneas, deje que el programador haga un seguimiento
independiente de cada invocacin definiendo la sobrecargaMethodNameAsync que toma un parmetro de
estado con valor de objeto, o un identificador de tarea, denominado userSuppliedState. Dicho parmetro
siempre debera ser el ltimo parmetro en la firma del mtodo MethodNameAsync.
Si la clase define la sobrecarga MethodNameAsync que toma un parmetro de estado con valor de objeto, o
un identificador de tarea, asegrese de realizar el seguimiento de la duracin de la operacin con ese
identificador de tarea y de devolverlo al controlador de finalizacin. Hay clases de ayuda disponibles para
asistir este proceso.
Si la clase define el mtodo MethodNameAsync sin el parmetro de estado y no admite varias invocaciones
simultneas, asegrese de que cualquier intento de invocar MethodNameAsync, antes de que haya finalizado
la invocacin previa de MethodNameAsync, produzca una excepcin InvalidOperationException.
En trminos generales, no produzca una excepcin si se invoca varias veces el mtodo MethodNameAsync sin
el parmetro userSuppliedState, de manera que haya varias operaciones pendientes. Puede provocar una
excepcin cuando la clase no puede controlar explcitamente esa situacin, pero se supone que los
desarrolladores pueden controlar todas estas devoluciones de llamada imposibles de distinguir.

Acceso a los resultados


Si se produjo un error durante la ejecucin de la operacin asincrnica, los resultados no deberan ser
accesibles. Asegrese de que el acceso a cualquier propiedad en AsyncCompletedEventArgs cuando Error no
es null provoca la excepcin a la que hace referencia Error. La clase AsyncCompletedEventArgsproporciona el
mtodo RaiseExceptionIfNecessary para este propsito.
Asegrese de que cualquier intento de obtener acceso al resultado produce una excepcin Invalid
OperationException, que informa de que se ha cancelado la operacin. Utilice el mtodo AsyncCompleted
EventArgs.RaiseExceptionIfNecessary para realizar la comprobacin anterior.

Informacin de progreso
Si es posible, permita que se genere informacin de progreso. Esto permitir a los desarrolladores ofrecer una
mejor experiencia al usuario de la aplicacin cuando utilicen la clase en cuestin.
Si implementa un evento ProgressChanged/MethodNameProgressChanged, asegrese de que no se ha
producido ningn evento de este tipo para ninguna operacin asincrnica especfica una vez producido el
evento MethodNameCompleted para dicha operacin.
Si se rellena el objeto ProgressChangedEventArgs estndar, asegrese 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.
Asegrese de que el evento ProgressChanged se provoca en el subproceso adecuado y en el momento
oportuno del ciclo de vida de la aplicacin. Para obtener ms informacin, vea la seccin Subprocesamiento y
contextos.

Implementacin de IsBusy
No exponga una propiedad IsBusy si la clase admite varias invocaciones simultneas. Por ejemplo, los
servidores proxy del servicio Web XML no exponen una propiedad IsBusy porque admiten varias invocaciones
simultneas de mtodos asincrnicos.
La propiedad IsBusy debera devolver true despus de haber llamado al mtodo MethodNameAsync y antes
de que se haya provocado el eventoMethodNameCompleted. Si no fuese as, debera devolver false. Los
componentes BackgroundWorker y WebClient son ejemplos de clases que exponen una propiedad IsBusy.

Cancelacin
Si es posible, permita la posibilidad de cancelacin. Esto permitir a los desarrolladores ofrecer una mejor
experiencia al usuario de la aplicacin cuando utilicen la clase en cuestin.
En caso de cancelacin, establezca el marcador Cancelled en el objeto AsyncCompletedEventArgs.
Asegrese de que cualquier intento de obtener acceso al resultado produce una excepcin Invalid
OperationException, que informa de que se ha cancelado la operacin. Utilice el mtodo AsyncCompleted
EventArgs.RaiseExceptionIfNecessary para realizar la comprobacin anterior.
Asegrese de que las llamadas a un mtodo de cancelacin se realicen correctamente y no produzcan nunca
una excepcin. Como norma general, no se notifica a los clientes si una operacin es realmente cancelable en
cualquier momento, ni tampoco si una cancelacin emitida con anterioridad ha tenido xito. Sin embargo,
siempre se notificar a la aplicacin si la cancelacin ha tenido xito, ya que la aplicacin participa en el
estado de realizacin.
Provoque el evento MethodNameCompleted cuando se cancele la operacin.

MCT: Luis Dueas Pag 33 de 80


Modelos para la Programacin Asincrnica en .NET

Errores y excepciones
Detecte cualquier excepcin que se produzca en la operacin asincrnica y establezca esa excepcin 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 aplicacin, incluidas las aplicaciones de Windows Forms y
ASP.NET. Se proporcionan dos clases de ayuda importantes para garantizar que la clase asincrnica se comporta
correctamente bajo cualquier modelo de aplicacin: AsyncOperation y AsyncOperationManager.
AsyncOperationManager proporciona un mtodo, CreateOperation, que devuelve un objeto AsyncOperation. El
mtodo MethodNameAsync llama a CreateOperation y la clase utiliza el objeto AsyncOperation devuelto para realizar el
seguimiento del perodo de duracin de la tarea asincrnica.
Para informar al cliente sobre el progreso, los resultados incrementales y la finalizacin, llame a los
mtodos 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.

Nota

Puede no seguir estas reglas si quiere contradecir explcitamente las directivas del modelo de aplicacin; pero,
incluso as, podr seguir beneficindose de las otras ventajas de utilizar el modelo asincrnico 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 ejecucin de
llamadas Post. Esto puede hacer que los eventos ProgressChanged se produzcan en un orden incorrecto. Si
desea tener una ejecucin serializada de llamadas Post, implemente e instale una clase System.
Threading.SynchronizationContext.

Instrucciones
Lo ideal es que cada invocacin de mtodo debera ser independiente de las otras. Debera evitar asociar las
invocaciones con recursos compartidos. Si fuese necesario compartir los recursos entre las invocaciones,
tendr que utilizar un mecanismo de sincronizacin apropiado en la implementacin.
No se recomiendan los diseos que exigen al cliente que implemente sincronizacin. Por ejemplo, si tiene un
mtodo asincrnico que recibe un objeto esttico global como parmetro, varias invocaciones simultneas de
dicho mtodo podran dar como resultado interbloqueos o datos daados.
Si implementa un mtodo con la sobrecarga de varias invocaciones (userState en la firma), la clase tendr que
administrar una coleccin de estados de usuario, o identificadores de tarea, y las operaciones pendientes
correspondientes. Esta coleccin se debera proteger con regiones lock, porque las distintas invocaciones
agregan y quitan objetos userState en la coleccin.
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 mtodo, ya que un delegado dado y el
tipo EventArgs no estn asociados a un nico mtodo. 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 aplicacin, 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.

2.1.4. Decidir cundo implementar el modelo asincrnico


basado en eventos
El Modelo asincrnico basado en evento proporciona un modelo para exponer el comportamiento asincrnico de una
clase. Con la introduccin de este modelo, .NET Framework define dos modelos para exponer el comportamiento
asincrnico: el Modelo asincrnico basado en la interfaz System.IAsyncResult y el modelo basado en eventos. En este
tema se explica cundo es adecuado implementar ambos modelos.

Principios generales
En general, siempre que sea posible debera exponer las caractersticas asincrnicas mediante el Modelo asincrnico
basado en evento. Sin embargo, hay algunos requisitos que no puede cumplir el modelo basado en eventos. En esos
casos, puede ser necesario implementar el modelo IAsyncResult adems del modelo basado en eventos.

MCT: Luis Dueas Pag 34 de 80


Modelos para la Programacin Asincrnica en .NET

Nota

Es raro que se implemente el modelo IAsyncResult sin que tambin se implemente el modelo basado en
eventos.

Instrucciones
La lista siguiente incluye instrucciones sobre cundo se debera implementar el Modelo asincrnico basado en evento:
Utilice el modelo basado en eventos como la API predeterminada para exponer el comportamiento
asincrnico de su clase.
No exponga el modelo IAsyncResult cuando su clase se utilice principalmente en una aplicacin cliente como,
por ejemplo, Windows Forms.
Exponga el modelo IAsyncResult slo cuando sea necesario para cumplir sus requisitos. Por ejemplo, la
compatibilidad con una API existente puede requerir que se exponga el modelo IAsyncResult.
No exponga el modelo IAsyncResult sin exponer tambin el modelo basado en evento.
Si debe exponer el modelo IAsyncResult, hgalo como una opcin avanzada. Por ejemplo, si genera un objeto
de servidor proxy, genere de forma predeterminada el modelo basado en eventos, con una opcin para
generar el modelo IAsyncResult.
Compile su implementacin del modelo basado en eventos en su implementacin del modelo IAsyncResult.
Evite exponer el modelo basado en eventos y el modelo IAsyncResult en la misma clase. Exponga el modelo
basado en eventos en las clases de "alto nivel" y el modelo IAsyncResult en clases de "nivel inferior". Por
ejemplo, compare el modelo basado en eventos en el componente WebClient con el modelo IAsyncResult en
la clase HttpRequest.
o Exponga el modelo basado en eventos y el modelo IAsyncResult en la misma clase cuando lo
requiera la compatibilidad. Por ejemplo, si ya ha publicado una API que utiliza el
modelo IAsyncResult, sera necesario conservar el modelo IAsyncResult para la compatibilidad con
versiones anteriores.
o Exponga el modelo basado en eventos y el modelo IAsyncResult en la misma clase si la complejidad
del modelo de objetos resultante no compensar la ventaja de separar las implementaciones. Es
mejor exponer ambos modelos en una sola clase que evitar exponer el modelo basado en evento.
o Si debe exponer tanto el modelo basado en eventos como el modelo IAsyncResult en una nica
clase, utilice EditorBrowsableAttribute establecido en Advanced para marcar la implementacin del
modelo IAsyncResult como caracterstica avanzada. Esto indica a los entornos de diseo, como el
IntelliSense de Visual Studio, que no deben mostrar las propiedades y mtodos
de IAsyncResult. Estas propiedades y mtodos todava son totalmente utilizables, pero el
desarrollador que trabaja con IntelliSense tiene una visin ms clara de la API.

Criterios para exponer el modelo IAsyncResult adems del modelo basado en


eventos
Aunque el Modelo asincrnico basado en evento tiene muchas ventajas en los escenarios mencionados anteriormente,
tiene algunos inconvenientes que debera conocer si el factor ms importante para sus aplicaciones es el rendimiento.
Hay tres escenarios que no prev el modelo basado en eventos as como el modelo IAsyncResult:
Bloquear la espera de un IAsyncResult
Bloquear la espera de muchos objetos IAsyncResult
Sondear la finalizacin en IAsyncResult
Puede afrontar estos escenarios utilizando el modelo basado en eventos, pero hacerlo resulta ms torpe que utilizar el
modelo IAsyncResult.
A menudo, los desarrolladores utilizan el modelo IAsyncResult para los servicios que normalmente tienen requisitos de
muy alto rendimiento. Por ejemplo, el escenario de sondeo de finalizacin es una tcnica de servidor de alto
rendimiento.
Adems, el modelo basado en eventos es menos eficaz que el modelo IAsyncResult porque crea ms objetos, sobre
todo EventArgs, y porque sincroniza los subprocesos.
La lista siguiente muestra algunas recomendaciones que seguir si decide utilizar el modelo IAsyncResult:
Exponga slo el modelo IAsyncResult cuando requiere especficamente la compatibilidad para los
objetos WaitHandle o IAsyncResult.
Exponga el modelo IAsyncResult slo cuando tenga una API existente que utilice el modelo IAsyncResult.
Si tiene una API existente basada en el modelo IAsyncResult, plantese exponer tambin el modelo basado en
eventos en su siguiente versin publicada.
Exponga el modelo IAsyncResult slo si requiere un rendimiento algo que haya comprobado que no puede
obtener con el modelo basado en eventos pero que s se puede conseguir con el modelo IAsyncResult.

MCT: Luis Dueas Pag 35 de 80


Modelos para la Programacin Asincrnica en .NET

2.1.5. Tutorial: Implementar un componente que admita el


modelo asincrnico basado en eventos
Si est escribiendo una clase con algunas operaciones que puedan incurrir en retrasos notables, puede probar a darle
funcionalidad asincrnica implementando Informacin general sobre el modelo asincrnico basado en eventos.
Este tutorial muestra cmo crear un componente que implemente el modelo asincrnico basado en eventos. Se
implementa utilizando clases de ayuda del espacio de nombres System.ComponentModel, lo que garantiza que el
componente funciona correctamente bajo cualquier modelo de aplicacin, incluso ASP.NET, aplicaciones de consola y
aplicaciones de Windows Forms. Este componente tambin se puede disear con un control PropertyGrid y con
diseadores personalizados propios.
Cuando haya terminado, tendr una aplicacin que calcula de forma asincrnica los nmeros primos. La aplicacin
tendr un subproceso de interfaz de usuario principal y un subproceso para cada clculo de nmero primo. Aunque
comprobar si un nmero elevado es primo puede llevar una cantidad de tiempo considerable, el subproceso de interfaz
de usuario principal no se ver interrumpido por este retraso, y el formulario permanecer receptivo durante el
clculo. Podr ejecutar tantos clculos como desee simultneamente, as como cancelar de forma selectiva los clculos
pendientes.
Las tareas ilustradas en este tutorial incluyen:
Crear el componente
Definir delegados y eventos asincrnicos pblicos
Definir delegados privados
Implementar eventos pblicos
Implementar el mtodo de finalizacin
Implementar los mtodos de trabajo
Implementar mtodos de inicio y cancelacin

Crear el componente
El primer paso es crear el componente que implementar el modelo asincrnico basado en eventos.

Para crear el componente


Cree una clase denominada PrimeNumberCalculator que herede de Component.
Definir delegados y eventos asincrnicos pblicos
Un componente se comunica con los clientes mediante eventos. El evento MethodNameCompleted avisa a los clientes
de la finalizacin de una tarea asincrnica y el eventoMethodNameProgressChanged informa a los clientes sobre el
progreso de dicha tarea.

Para definir eventos asincrnicos para los clientes de un componente:


1. Importe los espacios de nombres System.Threading y System.Collections.Specialized que estn en la parte
superior del archivo.
using System;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Globalization;
using System.Threading;
using System.Windows.Forms;
2. Antes de la definicin de la clase PrimeNumberCalculator, declare delegados para los eventos de progreso y
finalizacin.
public delegate void ProgressChangedEventHandler(
ProgressChangedEventArgs e);
public delegate void CalculatePrimeCompletedEventHandler(
object sender, CalculatePrimeCompletedEventArgs e);
3. En la definicin de la clase PrimeNumberCalculator, declare los eventos para informar a los clientes sobre el
progreso y la finalizacin de la tarea.
public event ProgressChangedEventHandler ProgressChanged;
public event CalculatePrimeCompletedEventHandler
CalculatePrimeCompleted;

MCT: Luis Dueas Pag 36 de 80


Modelos para la Programacin Asincrnica en .NET

4. Despus de la definicin de la clase PrimeNumberCalculator, derive la clase CalculatePrime


CompletedEventArgs para informar del resultado de cada clculo al controlador de eventos del cliente
correspondiente al evento CalculatePrimeCompleted. Adems de las propiedades AsyncCompleted
EventArgs, esta clase permite al cliente determinar qu nmero se ha comprobado, si es primo y, en caso de
que no lo sea, cul es el primer divisor.
public class CalculatePrimeCompletedEventArgs :
AsyncCompletedEventArgs
{
private int numberToTestValue = 0;
private int firstDivisorValue = 1;
private bool isPrimeValue;

public CalculatePrimeCompletedEventArgs(int numberToTest,


int firstDivisor, bool isPrime, Exception e, bool canceled,
object state) : base(e, canceled, state)
{
this.numberToTestValue = numberToTest;
this.firstDivisorValue = firstDivisor;
this.isPrimeValue = isPrime;
}

public int NumberToTest


{
get
{
// Raise an exception if the operation failed or
// was canceled.
RaiseExceptionIfNecessary();
// If the operation was successful, return the
// property value.
return numberToTestValue;
}
}

public int FirstDivisor


{
get
{
// Raise an exception if the operation failed or
// was canceled.
RaiseExceptionIfNecessary();
// If the operation was successful, return the
// property value.
return firstDivisorValue;
}
}

public bool IsPrime


{
get
{
// Raise an exception if the operation failed or
// was canceled.
RaiseExceptionIfNecessary();
// If the operation was successful, return the
// property value.
return isPrimeValue;
}
}
}

Punto de control
En este punto, ya puede compilar el componente.

Para probar el componente


Compile el componente.

MCT: Luis Dueas Pag 37 de 80


Modelos para la Programacin Asincrnica en .NET

Recibir dos advertencias del compilador:


warning CS0067: The event
'AsynchronousPatternExample.PrimeNumberCalculator.ProgressChanged' is
never used
warning CS0067: The event
'AsynchronousPatternExample.PrimeNumberCalculator.CalculatePrimeComplete
d' is never used
Estas advertencias se borrarn en la seccin siguiente.

Definir delegados privados


Los aspectos asincrnicos del componente PrimeNumberCalculator se implementan internamente con un delegado
especial conocido como SendOrPostCallback.SendOrPostCallback representa un mtodo de devolucin de llamada que
se ejecuta en un subproceso ThreadPool. El mtodo de devolucin de llamada debe tener una firma que tome un nico
parmetro de tipo Object, lo que significa que tendr que pasar informacin de estado entre los delegados de una clase
contenedora.

Para implementar el comportamiento asincrnico interno de un componente:


1. Declare y cree los delegados SendOrPostCallback en la clase PrimeNumberCalculator. Cree los
objetos SendOrPostCallback en un mtodo de utilidad denominado InitializeDelegates.
Necesitar dos delegados: uno para informar sobre el progreso al cliente y otro para informar sobre la
finalizacin al cliente.
private SendOrPostCallback onProgressReportDelegate;
private SendOrPostCallback onCompletedDelegate;
...
protected virtual void InitializeDelegates()
{
onProgressReportDelegate = new SendOrPostCallback(ReportProgress);
onCompletedDelegate = new SendOrPostCallback(CalculateCompleted);
}
2. Llame al mtodo InitializeDelegates en el constructor del componente.
public PrimeNumberCalculator()
{
InitializeComponent();
InitializeDelegates();
}
3. Declare un delegado en la clase PrimeNumberCalculator que controla el trabajo real que se va a hacer de
forma asincrnica. Este delegado ajusta el mtodo de trabajo que comprueba si un nmero es primo. El
delegado toma un parmetro AsyncOperation, que se utilizar para realizar el seguimiento de la duracin de
la operacin asincrnica.
private delegate void WorkerEventHandler(
int numberToCheck, AsyncOperation asyncOp);
4. Cree una coleccin para administrar la duracin de las operaciones asincrnicas pendientes. El cliente necesita
de alguna manera realizar un seguimiento de la ejecucin y finalizacin de las operaciones. Dicho seguimiento
se realiza pidindole al cliente que pase un smbolo (token) nico, o identificador de tarea, cuando realice la
llamada al mtodo asincrnico. El componente PrimeNumberCalculator debe mantener un registro de cada
llamada asociando el identificador de tarea a su invocacin correspondiente. Si el cliente pasa un identificador
de tarea que no es nico, el componente PrimeNumberCalculator debe provocar una excepcin.
El componentePrimeNumberCalculatormantiene un registro del identificador de tarea utilizando una clase
de coleccin especial denominada HybridDictionary. En la definicin de clase, cree un objeto Hybrid
Dictionary denominado userTokenToLifetime.
private HybridDictionary userStateToLifetime = new HybridDictionary();

Implementar eventos pblicos


Los componentes que implementan el modelo asincrnico basado en eventos se comunican con los clientes mediante
eventos. Estos eventos se invocan en el subproceso apropiado con la ayuda de la clase AsyncOperation.

Para provocar eventos en los clientes de un componente:


Implemente eventos pblicos para informar a los clientes. Necesitar un evento para informar sobre el
progreso y otro para informar de la finalizacin.
// This method is invoked via the AsyncOperation object,

MCT: Luis Dueas Pag 38 de 80


Modelos para la Programacin Asincrnica en .NET

// so it is guaranteed to be executed on the correct thread.


private void CalculateCompleted(object operationState)
{
CalculatePrimeCompletedEventArgs e =
operationState as CalculatePrimeCompletedEventArgs;
OnCalculatePrimeCompleted(e);
}

// This method is invoked via the AsyncOperation object,


// so it is guaranteed to be executed on the correct thread.
private void ReportProgress(object state)
{
ProgressChangedEventArgs e = state as ProgressChangedEventArgs;
OnProgressChanged(e);
}

protected void OnCalculatePrimeCompleted(


CalculatePrimeCompletedEventArgs e)
{
if (CalculatePrimeCompleted != null)
{
CalculatePrimeCompleted(this, e);
}
}

protected void OnProgressChanged(ProgressChangedEventArgs e)


{
if (ProgressChanged != null)
{
ProgressChanged(e);
}
}

Implementar el mtodo de finalizacin


El delegado de finalizacin es el mtodo que invocar el comportamiento asincrnico de subprocesamiento libre
subyacente una vez que finalice la operacin asincrnica, bien porque sta haya acabado correctamente, porque se haya
producido un error o por cancelacin. Esta invocacin se produce en un subproceso arbitrario.
En este mtodo es donde se quita el identificador de tarea del cliente de la coleccin interna de smbolos (token) de
cliente nicos. Este mtodo tambin pone trmino a la duracin de una operacin asincrnica determinada llamando al
mtodo PostOperationCompleted en el objeto AsyncOperation correspondiente. Esta llamada provoca en el subproceso
el evento de finalizacin adecuado para el modelo de aplicacin. Una vez llamado el mtodo PostOperationCompleted,
no es posible seguir utilizando esta instancia de AsyncOperation. Cualquier intento posterior de utilizarla producir una
excepcin.
La firma CompletionMethod debe contener toda la informacin de estado necesaria para describir el resultado de la
operacin asincrnica. Contiene informacin de estado del nmero comprobado por esta operacin asincrnica
especfica, si el nmero es primo, y el valor de su primer divisor, si el nmero no es primo. Tambin contiene
informacin de estado que describe cualquier excepcin que se haya producido y el objeto AsyncOperation
correspondiente a esta tarea determinada.
Para poner trmino a una operacin asincrnica:
Implemente el mtodo de finalizacin. Toma seis parmetros, que utiliza para rellenar CalculatePrime
CompletedEventArgs que se devuelve al cliente a travs deCalculatePrime CompletedEventHandler del
cliente. Quita el smbolo (token) del identificador de tarea del cliente de la coleccin interna y finaliza la
duracin de la operacin asincrnica con una llamada al mtodo PostOperationCompleted.
AsyncOperation calcula las referencias de la llamada al subproceso o contexto adecuado para el modelo de
aplicacin.
// This is the method that the underlying, free-threaded asynchronous
// behavior will invoke. This will happen on an arbitrary thread.
private void CompletionMethod(int numberToTest, int firstDivisor,
bool isPrime, Exception exception, bool canceled,
AsyncOperation asyncOp)
{
// If the task was not previously canceled,
// remove the task from the lifetime collection.
if (!canceled)
{

MCT: Luis Dueas Pag 39 de 80


Modelos para la Programacin Asincrnica en .NET

lock (userStateToLifetime.SyncRoot)
{
userStateToLifetime.Remove(asyncOp.UserSuppliedState);
}
}

// Package the results of the operation in a


// CalculatePrimeCompletedEventArgs.
CalculatePrimeCompletedEventArgs e =
new CalculatePrimeCompletedEventArgs(numberToTest,
firstDivisor, isPrime, exception, canceled,
asyncOp.UserSuppliedState);
// End the task. The asyncOp object is responsible
// for marshaling the call.
asyncOp.PostOperationCompleted(onCompletedDelegate, e);
// Note that after the call to OperationCompleted,
// asyncOp is no longer usable, and any attempt to use it
// will cause an exception to be thrown.
}

Punto de control
En este punto, ya puede compilar el componente.

Para probar el componente


Compile el componente.
Recibir una advertencia del compilador:
warning CS0169: The private field
'AsynchronousPatternExample.PrimeNumberCalculator.workerDelegate' is
never used
Esta advertencia se resolver en la seccin siguiente.

Implementar los mtodos de trabajo


Hasta el momento, ha implementado el cdigo asincrnico admitido para el componente PrimeNumberCalculator.
Ahora puede implementar el cdigo que hace el trabajo real. Implementar tres mtodos: CalculateWorker, Build
PrimeNumberList e IsPrime. Juntos, BuildPrimeNumberList e IsPrime constituyen un algoritmo muy conocido
denominado criba de Eratstenes, que determina si un nmero es primo buscando todos los nmeros primos hasta la
raz cuadrada del nmero probado. Si llegados a ese punto no se ha encontrado ningn divisor, el nmero en cuestin
es primo.
Si este componente fuese escrito para obtener una eficacia mxima, recordara todos los nmeros primos detectados
por distintas invocaciones para los distintos nmeros probados. Tambin buscara divisores triviales como el 2, el 3 y el
5. Sea como sea, con este ejemplo se pretende demostrar lo largas que pueden resultar las operaciones ejecutadas de
forma asincrnica, por lo que estas optimizaciones se dejan como ejercicio para quien las quiera realizar.
El mtodo CalculateWorker se ajusta en un delegado y se invoca de forma asincrnica con una llamada a
BeginInvoke

Nota

La informacin del progreso se implementa en el mtodo BuildPrimeNumberList. En equipos rpidos, se


pueden provocar eventos ProgressChanged en una sucesin rpida. El subproceso del cliente, en el que se
producen estos eventos, debe ser capaz de controlar esta situacin. El cdigo de interfaz de usuario puede verse
inundado de mensajes y es posible que no pueda seguir el ritmo y no responda.

Para ejecutar de forma asincrnica el clculo de nmeros primos:


1. Implemente el mtodo de utilidad TaskCanceled. Esto comprueba la coleccin de duracin de tarea para el
identificador de tarea determinado y devuelve true si no encuentra el identificador de tarea.
// Utility method for determining if a task has been canceled.
private bool TaskCanceled(object taskId)
{
return( userStateToLifetime[taskId] == null );
}
2. Implemente el mtodo CalculateWorker. Toma dos parmetros: un nmero que se va a comprobar y un
objeto AsyncOperation.

MCT: Luis Dueas Pag 40 de 80


Modelos para la Programacin Asincrnica en .NET

// This method performs the actual prime number computation.


// It is executed on the worker thread.
private void CalculateWorker(int numberToTest, AsyncOperation asyncOp)
{
bool isPrime = false;
int firstDivisor = 1;
Exception e = null;
// Check that the task is still active. The operation may have been
// canceled before the thread was scheduled.
if (!TaskCanceled(asyncOp.UserSuppliedState))
{
try
{
// Find all the prime numbers up to
// the square root of numberToTest.
ArrayList primes = BuildPrimeNumberList(numberToTest,
asyncOp);
// Now we have a list of primes less than numberToTest.
isPrime = IsPrime(primes, numberToTest, out firstDivisor);
}
catch (Exception ex)
{
e = ex;
}
}
//CalculatePrimeState calcState = new CalculatePrimeState(
// numberToTest,
// firstDivisor,
// isPrime,
// e,
// TaskCanceled(asyncOp.UserSuppliedState),
// asyncOp);
//this.CompletionMethod(calcState);
this.CompletionMethod(numberToTest, firstDivisor, isPrime,
e, TaskCanceled(asyncOp.UserSuppliedState), asyncOp);
//completionMethodDelegate(calcState);
}
3. Implemente BuildPrimeNumberList. Toma dos parmetros: el nmero que se va a comprobar y un
objeto AsyncOperation. AsyncOperation se utiliza para informar sobre el progreso y los resultados
incrementales. Esto garantiza que se llame a los controladores de eventos del cliente en el contexto o
subproceso apropiados para el modelo de aplicacin. Cuando BuildPrimeNumberList encuentra un nmero
primo, informa que ste es el resultado incremental al controlador de eventos del cliente correspondiente al
evento ProgressChanged. Esto requiere una clase derivada de ProgressChangedEventArgs, llamada
CalculatePrimeProgressChangedEventArgs, que tiene una propiedad agregada llamada LatestPrime
Number.
El mtodo BuildPrimeNumberList tambin llama peridicamente al mtodo TaskCanceled y se cierra si el
mtodo devuelve true.
// This method computes the list of prime numbers used by the
// IsPrime method.
private ArrayList BuildPrimeNumberList(int numberToTest,
AsyncOperation asyncOp)
{
ProgressChangedEventArgs e = null;
ArrayList primes = new ArrayList();
int firstDivisor;
int n = 5;
// Add the first prime numbers.
primes.Add(2);
primes.Add(3);
// Do the work.
while (n < numberToTest &&
!TaskCanceled( asyncOp.UserSuppliedState ) )
{
if (IsPrime(primes, n, out firstDivisor))
{
// Report to the client that a prime was found.

MCT: Luis Dueas Pag 41 de 80


Modelos para la Programacin Asincrnica en .NET

e = new CalculatePrimeProgressChangedEventArgs(n,
(int)((float)n / (float)numberToTest * 100),
asyncOp.UserSuppliedState);
asyncOp.Post(this.onProgressReportDelegate, e);
primes.Add(n);
// Yield the rest of this time slice.
Thread.Sleep(0);
}
// Skip even numbers.
n += 2;
}
return primes;
}
4. Implemente IsPrime. Toma tres parmetros: una lista de nmeros primos conocidos, el nmero que se quiere
comprobar y un parmetro de salida para el primer divisor encontrado. Dada la lista de nmeros primos,
determina si el nmero que se est comprobando es primo.
// This method tests n for primality against the list of
// prime numbers contained in the primes parameter.
private bool IsPrime(ArrayList primes, int n, out int firstDivisor)
{
bool foundDivisor = false;
bool exceedsSquareRoot = false;
int i = 0;
int divisor = 0;
firstDivisor = 1;
// Stop the search if: there are no more primes in the list,
// there is a divisor of n in the list, or there is a prime that
// is larger than the square root of n.
while ((i < primes.Count) && !foundDivisor && !exceedsSquareRoot)
{
// The divisor variable will be the smallest
// prime number not yet tried.
divisor = (int)primes[i++];
// Determine whether the divisor is greater
// than the square root of n.
if (divisor * divisor > n)
{
exceedsSquareRoot = true;
}
// Determine whether the divisor is a factor of n.
else if (n % divisor == 0)
{
firstDivisor = divisor;
foundDivisor = true;
}
}
return !foundDivisor;
}
5. Derive CalculatePrimeProgressChangedEventArgs de ProgressChangedEventArgs. Esta clase es necesaria
para informar sobre los resultados incrementales al controlador de eventos del cliente correspondiente al
evento ProgressChanged. Tiene una propiedad adicional denominada LatestPrimeNumber.
public class CalculatePrimeProgressChangedEventArgs :
ProgressChangedEventArgs
{
private int latestPrimeNumberValue = 1;

public CalculatePrimeProgressChangedEventArgs(
int latestPrime, int progressPercentage,
object userToken) : base( progressPercentage, userToken )
{
this.latestPrimeNumberValue = latestPrime;
}

public int LatestPrimeNumber


{

MCT: Luis Dueas Pag 42 de 80


Modelos para la Programacin Asincrnica en .NET

get
{
return latestPrimeNumberValue;
}
}
}

Punto de control
En este punto, ya puede compilar el componente.

Para probar el componente


Compile el componente.
Lo nico que falta por escribir son los mtodos para iniciar y cancelar operaciones asincrnicas, Calculate
PrimeAsyncyCancelAsync.

Implementar los mtodos de inicio y cancelacin


Inicie el mtodo de trabajo en su propio subproceso llamando a BeginInvoke en el delegado que lo incluye. Para
administrar la duracin de una operacin asincrnica determinada, llame al mtodo CreateOperation en la clase de
ayuda AsyncOperationManager. Esto devuelve un objeto AsyncOperation, que calcula las referencias de las llamadas en
los controladores de eventos del cliente para el contexto o subproceso apropiado.
Puede cancelar una operacin pendiente determinada llamando a PostOperationCompleted en su AsyncOperation
correspondiente. Esto finaliza esa operacin y cualquier llamada subsiguiente a su AsyncOperation producir una
excepcin.

Para implementar la funcionalidad de inicio y cancelacin:


1. Implemente el mtodo CalculatePrimeAsync. Asegrese de que el smbolo (token) o identificador de tarea
proporcionado por el cliente es nico, frente a todos los smbolos (token) que representan tareas pendientes
en ese momento. Si el cliente pasa un smbolo (token) que no es nico, CalculatePrimeAsync produce una
excepcin. De lo contrario, el smbolo (token) se agrega a la coleccin de identificadores de tarea.
// This method starts an asynchronous calculation.
// First, it checks the supplied task ID for uniqueness.
// If taskId is unique, it creates a new WorkerEventHandler
// and calls its BeginInvoke method to start the calculation.
public virtual void CalculatePrimeAsync(int numberToTest, object taskId)
{
// Create an AsyncOperation for taskId.
AsyncOperation asyncOp =AsyncOperationManager.CreateOperation(taskId);
// Multiple threads will access the task dictionary,
// so it must be locked to serialize access.
lock (userStateToLifetime.SyncRoot)
{
if (userStateToLifetime.Contains(taskId))
{
throw new ArgumentException(
"Task ID parameter must be unique", "taskId");
}
userStateToLifetime[taskId] = asyncOp;
}
// Start the asynchronous operation.
WorkerEventHandler workerDelegate = new WorkerEventHandler
(CalculateWorker);
workerDelegate.BeginInvoke(numberToTest, asyncOp, null, null);
}
2. Implemente el mtodo CancelAsync. Si existe el parmetro taskId en la coleccin de smbolos (token), se
quita. Esto evita la ejecucin de las tareas canceladas que no han comenzado. Si la tarea est ejecutndose, el
mtodo BuildPrimeNumberList se cierra cuando detecta que se ha quitado el identificador de tarea de la
coleccin de duracin.
// This method cancels a pending asynchronous operation.
public void CancelAsync(object taskId)
{
AsyncOperation asyncOp = userStateToLifetime[taskId] as
AsyncOperation;
if (asyncOp != null)

MCT: Luis Dueas Pag 43 de 80


Modelos para la Programacin Asincrnica en .NET

{
lock (userStateToLifetime.SyncRoot)
{
userStateToLifetime.Remove(taskId);
}
}
}

Punto de control
En este punto, ya puede compilar el componente.

Para probar el componente


Compile el componente.
El componente PrimeNumberCalculator ya est completo y listo para su uso.

Pasos siguientes
Puede completar este ejemplo escribiendo CalculatePrime, el equivalente sincrnico del mtodo CalculatePrime
Async. Esto har que el componentePrimeNumberCalculator cumpla plenamente las especificaciones del modelo
asincrnico basado en eventos.
Puede mejorar este ejemplo conservando la lista de todos los nmeros primos detectados por las distintas invocaciones
de los distintos nmeros probados. Si se usa este procedimiento, cada tarea se beneficiar del trabajo realizado por
tareas anteriores. Recuerde proteger esta lista con regiones lock, de manera que se serialice el acceso a la lista por parte
de subprocesos diferentes.
Tambin puede mejorar este ejemplo probando divisores triviales, como el 2, el 3 y el 5.

2.1.5.1. Cmo: Implementar un componente que admita el


modelo asincrnico basado en eventos
En el ejemplo de cdigo siguiente se implementa un componente con un mtodo asincrnico, segn Informacin
general sobre el modelo asincrnico basado en eventos. El componente es una calculadora de nmero primo que utiliza
el algoritmo de Eratosthenes o de Sieve para determinar si un nmero es primo o compuesto.
Visual Studio ofrece una amplia compatibilidad para esta tarea.

Ejemplo
using System;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Globalization;
using System.Threading;
using System.Windows.Forms;
/////////////////////////////////////////////////////////////
#region PrimeNumberCalculator Implementation

public delegate void ProgressChangedEventHandler(


ProgressChangedEventArgs e);

public delegate void CalculatePrimeCompletedEventHandler(


object sender, CalculatePrimeCompletedEventArgs e);

// This class implements the Event-based Asynchronous Pattern.


// It asynchronously computes whether a number is prime or
// composite (not prime).
public class PrimeNumberCalculator : Component
{
private delegate void WorkerEventHandler(
int numberToCheck, AsyncOperation asyncOp);
private SendOrPostCallback onProgressReportDelegate;
private SendOrPostCallback onCompletedDelegate;
private HybridDictionary userStateToLifetime =
new HybridDictionary();
private System.ComponentModel.Container components = null;

MCT: Luis Dueas Pag 44 de 80


Modelos para la Programacin Asincrnica en .NET

/////////////////////////////////////////////////////////////
#region Public events

public event ProgressChangedEventHandler ProgressChanged;


public event CalculatePrimeCompletedEventHandler
CalculatePrimeCompleted;

#endregion

/////////////////////////////////////////////////////////////
#region Construction and destruction

public PrimeNumberCalculator(IContainer container)


{
container.Add(this);
InitializeComponent();
InitializeDelegates();
}

public PrimeNumberCalculator()
{
InitializeComponent();
InitializeDelegates();
}

protected virtual void InitializeDelegates()


{
onProgressReportDelegate = new SendOrPostCallback(ReportProgress);
onCompletedDelegate = new SendOrPostCallback(CalculateCompleted);
}

protected override void Dispose(bool disposing)


{
if (disposing)
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose(disposing);
}

#endregion // Construction and destruction

/////////////////////////////////////////////////////////////
#region Implementation

// This method starts an asynchronous calculation.


// First, it checks the supplied task ID for uniqueness.
// If taskId is unique, it creates a new WorkerEventHandler
// and calls its BeginInvoke method to start the calculation.
public virtual void CalculatePrimeAsync(int numberToTest,object taskId)
{
// Create an AsyncOperation for taskId.
AsyncOperation asyncOp = AsyncOperationManager.CreateOperation
(taskId);
// Multiple threads will access the task dictionary,
// so it must be locked to serialize access.
lock (userStateToLifetime.SyncRoot)
{
if (userStateToLifetime.Contains(taskId))
{
throw new ArgumentException(
"Task ID parameter must be unique", "taskId");
}

MCT: Luis Dueas Pag 45 de 80


Modelos para la Programacin Asincrnica en .NET

userStateToLifetime[taskId] = asyncOp;
}

// Start the asynchronous operation.


WorkerEventHandler workerDelegate = new
WorkerEventHandler(CalculateWorker);
workerDelegate.BeginInvoke(numberToTest, asyncOp, null, null);
}

// Utility method for determining if a task has been canceled.


private bool TaskCanceled(object taskId)
{
return( userStateToLifetime[taskId] == null );
}

// This method cancels a pending asynchronous operation.


public void CancelAsync(object taskId)
{
AsyncOperation asyncOp = userStateToLifetime[taskId] as
AsyncOperation;
if (asyncOp != null)
{
lock (userStateToLifetime.SyncRoot)
{
userStateToLifetime.Remove(taskId);
}
}
}

// This method performs the actual prime number computation.


// It is executed on the worker thread.
private void CalculateWorker(int numberToTest, AsyncOperation asyncOp)
{
bool isPrime = false;
int firstDivisor = 1;
Exception e = null;
// Check that the task is still active. The operation may have
// been canceled before the thread was scheduled.
if (!TaskCanceled(asyncOp.UserSuppliedState))
{
try
{
// Find all the prime numbers up to
// the square root of numberToTest.
ArrayList primes = BuildPrimeNumberList(
numberToTest, asyncOp);
// Now we have a list of primes less than numberToTest.
isPrime = IsPrime(primes, numberToTest, out firstDivisor);
}
catch (Exception ex)
{
e = ex;
}
}
//CalculatePrimeState calcState = new CalculatePrimeState(
// numberToTest,
// firstDivisor,
// isPrime,
// e,
// TaskCanceled(asyncOp.UserSuppliedState),
// asyncOp);
//this.CompletionMethod(calcState);
this.CompletionMethod(numberToTest, firstDivisor, isPrime, e,
TaskCanceled(asyncOp.UserSuppliedState), asyncOp);
//completionMethodDelegate(calcState);
}

MCT: Luis Dueas Pag 46 de 80


Modelos para la Programacin Asincrnica en .NET

// This method computes the list of prime numbers used by the


// IsPrime method.
private ArrayList BuildPrimeNumberList(int numberToTest,
AsyncOperation asyncOp)
{
ProgressChangedEventArgs e = null;
ArrayList primes = new ArrayList();
int firstDivisor;
int n = 5;
// Add the first prime numbers.
primes.Add(2);
primes.Add(3);
// Do the work.
while(n<numberToTest&&!TaskCanceled(asyncOp.UserSuppliedState))
{
if (IsPrime(primes, n, out firstDivisor))
{
// Report to the client that a prime was found.
e = new CalculatePrimeProgressChangedEventArgs(n,
(int)((float)n / (float)numberToTest * 100),
asyncOp.UserSuppliedState);
asyncOp.Post(this.onProgressReportDelegate, e);
primes.Add(n);
// Yield the rest of this time slice.
Thread.Sleep(0);
}
// Skip even numbers.
n += 2;
}
return primes;
}

// This method tests n for primality against the list of


// prime numbers contained in the primes parameter.
private bool IsPrime(ArrayList primes, int n, out int firstDivisor)
{
bool foundDivisor = false;
bool exceedsSquareRoot = false;
int i = 0;
int divisor = 0;
firstDivisor = 1;
// Stop the search if: there are no more primes in the list,
// there is a divisor of n in the list, or
// there is a prime that is larger than the square root of n.
while ((i < primes.Count) && !foundDivisor && !exceedsSquareRoot)
{
// The divisor variable will be the smallest
// prime number not yet tried.
divisor = (int)primes[i++];
// Determine whether the divisor is greater
// than the square root of n.
if (divisor * divisor > n)
{
exceedsSquareRoot = true;
}
// Determine whether the divisor is a factor of n.
else if (n % divisor == 0)
{
firstDivisor = divisor;
foundDivisor = true;
}
}
return !foundDivisor;
}

// This method is invoked via the AsyncOperation object,


// so it is guaranteed to be executed on the correct thread.

MCT: Luis Dueas Pag 47 de 80


Modelos para la Programacin Asincrnica en .NET

private void CalculateCompleted(object operationState)


{
CalculatePrimeCompletedEventArgs e =
operationState as CalculatePrimeCompletedEventArgs;
OnCalculatePrimeCompleted(e);
}

// This method is invoked via the AsyncOperation object,


// so it is guaranteed to be executed on the correct thread.
private void ReportProgress(object state)
{
ProgressChangedEventArgs e = state as ProgressChangedEventArgs;
OnProgressChanged(e);
}

protected void OnCalculatePrimeCompleted(


CalculatePrimeCompletedEventArgs e)
{
if (CalculatePrimeCompleted != null)
{
CalculatePrimeCompleted(this, e);
}
}

protected void OnProgressChanged(ProgressChangedEventArgs e)


{
if (ProgressChanged != null)
{
ProgressChanged(e);
}
}

// This is the method that the underlying, free-threaded asynchronous


// behavior will invoke. This will happen on an arbitrary thread.
private void CompletionMethod(int numberToTest, int firstDivisor,
bool isPrime, Exception exception, bool canceled,
AsyncOperation asyncOp)
{
// If the task was not previously canceled,
// remove the task from the lifetime collection.
if (!canceled)
{
lock (userStateToLifetime.SyncRoot)
{
userStateToLifetime.Remove(asyncOp.UserSuppliedState);
}
}

// Package the results of the operation in a


// CalculatePrimeCompletedEventArgs.
CalculatePrimeCompletedEventArgs e =
new CalculatePrimeCompletedEventArgs(numberToTest,
firstDivisor, isPrime, exception, canceled,
asyncOp.UserSuppliedState);

// End the task. The asyncOp object is responsible


// for marshaling the call.
asyncOp.PostOperationCompleted(onCompletedDelegate, e);
// Note that after the call to OperationCompleted,
// asyncOp is no longer usable, and any attempt to use it
// will cause an exception to be thrown.
}
#endregion

/////////////////////////////////////////////////////////////
#region Component Designer generated code

MCT: Luis Dueas Pag 48 de 80


Modelos para la Programacin Asincrnica en .NET

private void InitializeComponent()


{
components = new System.ComponentModel.Container();
}

#endregion
}

public class CalculatePrimeProgressChangedEventArgs :


ProgressChangedEventArgs
{
private int latestPrimeNumberValue = 1;

public CalculatePrimeProgressChangedEventArgs(int latestPrime,


int progressPercentage, object userToken) :
base(progressPercentage, userToken)
{
this.latestPrimeNumberValue = latestPrime;
}

public int LatestPrimeNumber


{
get
{
return latestPrimeNumberValue;
}
}
}

public class CalculatePrimeCompletedEventArgs : AsyncCompletedEventArgs


{
private int numberToTestValue = 0;
private int firstDivisorValue = 1;
private bool isPrimeValue;

public CalculatePrimeCompletedEventArgs(int numberToTest,


int firstDivisor, bool isPrime, Exception e, bool canceled,
object state) : base(e, canceled, state)
{
this.numberToTestValue = numberToTest;
this.firstDivisorValue = firstDivisor;
this.isPrimeValue = isPrime;
}

public int NumberToTest


{
get
{
// Raise an exception if the operation failed or
// was canceled.
RaiseExceptionIfNecessary();
// If the operation was successful, return the
// property value.
return numberToTestValue;
}
}

public int FirstDivisor


{
get
{
// Raise an exception if the operation failed or
// was canceled.
RaiseExceptionIfNecessary();
// If the operation was successful, return the
// property value.
return firstDivisorValue;

MCT: Luis Dueas Pag 49 de 80


Modelos para la Programacin Asincrnica en .NET

}
}

public bool IsPrime


{
get
{
// Raise an exception if the operation failed or
// was canceled.
RaiseExceptionIfNecessary();
// If the operation was successful, return the
// property value.
return isPrimeValue;
}
}
}
#endregion

2.1.5.2. Cmo: Implementar un cliente en un modelo asincrnico


basado en eventos
El siguiente ejemplo de cdigo muestra cmo utilizar un componente conforme a Informacin general sobre el modelo
asincrnico basado en eventos. El formulario de este ejemplo utiliza el componente PrimeNumberCalculator, que se
describe en Cmo: Implementar un componente que admita el modelo asincrnico basado en eventos.
Cuando ejecute un proyecto que utilice este ejemplo, ver un formulario de clculo de nmeros primos ("Prime Number
Calculator") con una cuadrcula y dos botones: Iniciar tarea nueva y Cancelar. Puede hacer clic en el botn Iniciar
tarea nueva varias veces seguidas; por cada clic, una operacin asincrnica iniciar un clculo para determinar si un
nmero de prueba generado aleatoriamente es primo. El formulario mostrar peridicamente el progreso y los
resultados incrementales. A cada operacin se le asigna un identificador de tarea nico. El resultado del clculo se
muestra en la columna Resultado; si el nmero de prueba no es primo, se le asigna la etiqueta Compuesto y se
muestra su primer divisor.
Cualquier operacin pendiente puede cancelarse con el botn Cancelar. Se pueden realizar selecciones mltiples.

Nota

La mayora de los nmeros no sern primos. Si tras realizar varias operaciones no encuentra ningn nmero primo,
simplemente inicie ms tareas; acabar encontrando algn nmero primo.

Ejemplo
using System;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Globalization;
using System.Threading;
using System.Windows.Forms;

namespace AsyncOperationManagerExample
{
// This form tests the PrimeNumberCalculator component.
public class PrimeNumberCalculatorMain : System.Windows.Forms.Form
{
/////////////////////////////////////////////////////////////
// Private fields
#region Private fields

private PrimeNumberCalculator primeNumberCalculator1;


private System.Windows.Forms.GroupBox taskGroupBox;
private System.Windows.Forms.ListView listView1;
private System.Windows.Forms.ColumnHeader taskIdColHeader;
private System.Windows.Forms.ColumnHeader progressColHeader;
private System.Windows.Forms.ColumnHeader currentColHeader;
private System.Windows.Forms.Panel buttonPanel;

MCT: Luis Dueas Pag 50 de 80


Modelos para la Programacin Asincrnica en .NET

private System.Windows.Forms.Panel panel2;


private System.Windows.Forms.Button startAsyncButton;
private System.Windows.Forms.Button cancelButton;
private System.Windows.Forms.ColumnHeader testNumberColHeader;
private System.Windows.Forms.ColumnHeader resultColHeader;
private System.Windows.Forms.ColumnHeader firstDivisorColHeader;
private System.ComponentModel.IContainer components;
private int progressCounter;
private int progressInterval = 100;

#endregion // Private fields

/////////////////////////////////////////////////////////////
// Construction and destruction
#region Private fields
public PrimeNumberCalculatorMain ()
{
// Required for Windows Form Designer support
InitializeComponent();

// Hook up event handlers.


this.primeNumberCalculator1.CalculatePrimeCompleted +=
new CalculatePrimeCompletedEventHandler(
primeNumberCalculator1_CalculatePrimeCompleted);

this.primeNumberCalculator1.ProgressChanged +=
new ProgressChangedEventHandler(
primeNumberCalculator1_ProgressChanged);

this.listView1.SelectedIndexChanged +=
new EventHandler(listView1_SelectedIndexChanged);
}

protected override void Dispose( bool disposing )


{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}

#endregion // Construction and destruction

/////////////////////////////////////////////////////////////
#region Implementation

// This event handler selects a number randomly to test for primality.


// It then starts the asynchronous calculation by calling the
// PrimeNumberCalculator component's CalculatePrimeAsync method.
private void startAsyncButton_Click (
System.Object sender, System.EventArgs e)
{
// Randomly choose test numbers up to 200,000 for primality.
Random rand = new Random();
int testNumber = rand.Next(200000);
// Task IDs are Guids.
Guid taskId = Guid.NewGuid();
this.AddListViewItem(taskId, testNumber);
// Start the asynchronous task.
this.primeNumberCalculator1.CalculatePrimeAsync(testNumber, taskId);
}

private void listView1_SelectedIndexChanged(object sender,EventArgs e)

MCT: Luis Dueas Pag 51 de 80


Modelos para la Programacin Asincrnica en .NET

{
this.cancelButton.Enabled = CanCancel();
}

// This event handler cancels all pending tasks that are


// selected in the ListView control.
private void cancelButton_Click(System.Object sender,System.EventArgs e)
{
Guid taskId = Guid.Empty;
// Cancel all selected tasks.
foreach(ListViewItem lvi in this.listView1.SelectedItems)
{
// Tasks that have been completed or canceled have their
// corresponding ListViewItem.Tag property set to null.
if (lvi.Tag != null)
{
taskId = (Guid)lvi.Tag;
this.primeNumberCalculator1.CancelAsync(taskId);
lvi.Selected = false;
}
}
cancelButton.Enabled = false;
}

// This event handler updates the ListView control when the


// PrimeNumberCalculator raises the ProgressChanged event.
// On fast computers, the PrimeNumberCalculator can raise many
// successive ProgressChanged events, so the user interface
// may be flooded with messages. To prevent the user interface
// from hanging, progress is only reported at intervals.
private void primeNumberCalculator1_ProgressChanged(
ProgressChangedEventArgs e)
{
if (this.progressCounter++ % this.progressInterval == 0)
{
Guid taskId = (Guid)e.UserState;
if (e is CalculatePrimeProgressChangedEventArgs)
{
CalculatePrimeProgressChangedEventArgs cppcea =
e as CalculatePrimeProgressChangedEventArgs;
this.UpdateListViewItem(taskId,
cppcea.ProgressPercentage, cppcea.LatestPrimeNumber);
}
else
{
this.UpdateListViewItem(taskId, e.ProgressPercentage);
}
}
else if (this.progressCounter > this.progressInterval)
{
this.progressCounter = 0;
}
}

// This event handler updates the ListView control when the


// PrimeNumberCalculator raises the CalculatePrimeCompleted
// event. The ListView item is updated with the appropriate
// outcome of the calculation: Canceled, Error, or result.
private void primeNumberCalculator1_CalculatePrimeCompleted(
object sender, CalculatePrimeCompletedEventArgs e)
{
Guid taskId = (Guid)e.UserState;
if (e.Cancelled)
{
string result = "Canceled";
ListViewItem lvi = UpdateListViewItem(taskId, result);
if (lvi != null)

MCT: Luis Dueas Pag 52 de 80


Modelos para la Programacin Asincrnica en .NET

{
lvi.BackColor = Color.Pink;
lvi.Tag = null;
}
}
else if (e.Error != null)
{
string result = "Error";
ListViewItem lvi = UpdateListViewItem(taskId, result);
if (lvi != null)
{
lvi.BackColor = Color.Red;
lvi.ForeColor = Color.White;
lvi.Tag = null;
}
}
else
{
bool result = e.IsPrime;
ListViewItem lvi = UpdateListViewItem(taskId, result,
e.FirstDivisor);
if (lvi != null)
{
lvi.BackColor = Color.LightGray;
lvi.Tag = null;
}
}
}

#endregion // Implementation

/////////////////////////////////////////////////////////////
#region Private Methods

private ListViewItem AddListViewItem(Guid guid, int testNumber)


{
ListViewItem lvi = new ListViewItem();
lvi.Text = testNumber.ToString(
CultureInfo.CurrentCulture.NumberFormat);
lvi.SubItems.Add("Not Started");
lvi.SubItems.Add("1");
lvi.SubItems.Add(guid.ToString());
lvi.SubItems.Add("---");
lvi.SubItems.Add("---");
lvi.Tag = guid;
this.listView1.Items.Add( lvi );
return lvi;
}

private ListViewItem UpdateListViewItem(Guid guid,


int percentComplete, int current)
{
ListViewItem lviRet = null;
foreach (ListViewItem lvi in this.listView1.Items)
{
if (lvi.Tag != null)
{
if ((Guid)lvi.Tag == guid)
{
lvi.SubItems[1].Text = percentComplete.ToString(
CultureInfo.CurrentCulture.NumberFormat);
lvi.SubItems[2].Text = current.ToString(
CultureInfo.CurrentCulture.NumberFormat);
lviRet = lvi;
break;
}
}

MCT: Luis Dueas Pag 53 de 80


Modelos para la Programacin Asincrnica en .NET

}
return lviRet;
}

private ListViewItem UpdateListViewItem(Guid guid,


int percentComplete, int current, bool result,
int firstDivisor)
{
ListViewItem lviRet = null;
foreach (ListViewItem lvi in this.listView1.Items)
{
if ((Guid)lvi.Tag == guid)
{
lvi.SubItems[1].Text = percentComplete.ToString(
CultureInfo.CurrentCulture.NumberFormat);
lvi.SubItems[2].Text = current.ToString(
CultureInfo.CurrentCulture.NumberFormat);
lvi.SubItems[4].Text = result ? "Prime" : "Composite";
lvi.SubItems[5].Text = firstDivisor.ToString(
CultureInfo.CurrentCulture.NumberFormat);
lviRet = lvi;
break;
}
}
return lviRet;
}

private ListViewItem UpdateListViewItem(Guid guid, int percentComplete)


{
ListViewItem lviRet = null;
foreach (ListViewItem lvi in this.listView1.Items)
{
if (lvi.Tag != null)
{
if ((Guid)lvi.Tag == guid)
{
lvi.SubItems[1].Text = percentComplete.ToString(
CultureInfo.CurrentCulture.NumberFormat);
lviRet = lvi;
break;
}
}
}
return lviRet;
}

private ListViewItem UpdateListViewItem(Guid guid, bool result,


int firstDivisor)
{
ListViewItem lviRet = null;
foreach (ListViewItem lvi in this.listView1.Items)
{
if (lvi.Tag != null)
{
if ((Guid)lvi.Tag == guid)
{
lvi.SubItems[4].Text = result ? "Prime" : "Composite";
lvi.SubItems[5].Text = firstDivisor.ToString(
CultureInfo.CurrentCulture.NumberFormat);
lviRet = lvi;
break;
}
}
}
return lviRet;
}

MCT: Luis Dueas Pag 54 de 80


Modelos para la Programacin Asincrnica en .NET

private ListViewItem UpdateListViewItem(Guid guid, string result)


{
ListViewItem lviRet = null;
foreach (ListViewItem lvi in this.listView1.Items)
{
if (lvi.Tag != null)
{
if ((Guid)lvi.Tag == guid)
{
lvi.SubItems[4].Text = result;
lviRet = lvi;
break;
}
}
}
return lviRet;
}

private bool CanCancel()


{
bool oneIsActive = false;
foreach(ListViewItem lvi in this.listView1.SelectedItems)
{
if (lvi.Tag != null)
{
oneIsActive = true;
break;
}
}
return( oneIsActive == true );
}

#endregion

#region Windows Form Designer generated code

private void InitializeComponent()


{
this.components = new System.ComponentModel.Container();
this.taskGroupBox = new System.Windows.Forms.GroupBox();
this.buttonPanel = new System.Windows.Forms.Panel();
this.cancelButton = new System.Windows.Forms.Button();
this.startAsyncButton = new System.Windows.Forms.Button();
this.listView1 = new System.Windows.Forms.ListView();
this.testNumberColHeader = new
System.Windows.Forms.ColumnHeader();
this.progressColHeader = new System.Windows.Forms.ColumnHeader();
this.currentColHeader = new System.Windows.Forms.ColumnHeader();
this.taskIdColHeader = new System.Windows.Forms.ColumnHeader();
this.resultColHeader = new System.Windows.Forms.ColumnHeader();
this.firstDivisorColHeader = new
System.Windows.Forms.ColumnHeader();
this.panel2 = new System.Windows.Forms.Panel();
this.primeNumberCalculator1 = new AsyncOperationManagerExample.
PrimeNumberCalculator(this.components);
this.taskGroupBox.SuspendLayout();
this.buttonPanel.SuspendLayout();
this.SuspendLayout();
//
// taskGroupBox
//
this.taskGroupBox.Controls.Add(this.buttonPanel);
this.taskGroupBox.Controls.Add(this.listView1);
this.taskGroupBox.Dock = System.Windows.Forms.DockStyle.Fill;
this.taskGroupBox.Location = new System.Drawing.Point(0, 0);
this.taskGroupBox.Name = "taskGroupBox";
this.taskGroupBox.Size = new System.Drawing.Size(608, 254);

MCT: Luis Dueas Pag 55 de 80


Modelos para la Programacin Asincrnica en .NET

this.taskGroupBox.TabIndex = 1;
this.taskGroupBox.TabStop = false;
this.taskGroupBox.Text = "Tasks";
//
// buttonPanel
//
this.buttonPanel.Controls.Add(this.cancelButton);
this.buttonPanel.Controls.Add(this.startAsyncButton);
this.buttonPanel.Dock = System.Windows.Forms.DockStyle.Bottom;
this.buttonPanel.Location = new System.Drawing.Point(3, 176);
this.buttonPanel.Name = "buttonPanel";
this.buttonPanel.Size = new System.Drawing.Size(602, 75);
this.buttonPanel.TabIndex = 1;
//
// cancelButton
//
this.cancelButton.Enabled = false;
this.cancelButton.Location = new System.Drawing.Point(128, 24);
this.cancelButton.Name = "cancelButton";
this.cancelButton.Size = new System.Drawing.Size(88, 23);
this.cancelButton.TabIndex = 1;
this.cancelButton.Text = "Cancel";
this.cancelButton.Click += new
System.EventHandler(this.cancelButton_Click);
//
// startAsyncButton
//
this.startAsyncButton.Location = new System.Drawing.Point(24, 24);
this.startAsyncButton.Name = "startAsyncButton";
this.startAsyncButton.Size = new System.Drawing.Size(88, 23);
this.startAsyncButton.TabIndex = 0;
this.startAsyncButton.Text = "Start New Task";
this.startAsyncButton.Click += new
System.EventHandler(this.startAsyncButton_Click);
//
// listView1
//
this.listView1.Columns.AddRange(new
System.Windows.Forms.ColumnHeader[] {this.testNumberColHeader,
this.progressColHeader, this.currentColHeader,
this.taskIdColHeader, this.resultColHeader,
this.firstDivisorColHeader});
this.listView1.Dock = System.Windows.Forms.DockStyle.Fill;
this.listView1.FullRowSelect = true;
this.listView1.GridLines = true;
this.listView1.Location = new System.Drawing.Point(3, 16);
this.listView1.Name = "listView1";
this.listView1.Size = new System.Drawing.Size(602, 160);
this.listView1.TabIndex = 0;
this.listView1.View = System.Windows.Forms.View.Details;
//
// testNumberColHeader
//
this.testNumberColHeader.Text = "Test Number";
this.testNumberColHeader.Width = 80;
//
// progressColHeader
//
this.progressColHeader.Text = "Progress";
//
// currentColHeader
//
this.currentColHeader.Text = "Current";
//
// taskIdColHeader
//
this.taskIdColHeader.Text = "Task ID";

MCT: Luis Dueas Pag 56 de 80


Modelos para la Programacin Asincrnica en .NET

this.taskIdColHeader.Width = 200;
//
// resultColHeader
//
this.resultColHeader.Text = "Result";
this.resultColHeader.Width = 80;
//
// firstDivisorColHeader
//
this.firstDivisorColHeader.Text = "First Divisor";
this.firstDivisorColHeader.Width = 80;
//
// panel2
//
this.panel2.Location = new System.Drawing.Point(200, 128);
this.panel2.Name = "panel2";
this.panel2.TabIndex = 2;
//
// PrimeNumberCalculatorMain
//
this.ClientSize = new System.Drawing.Size(608, 254);
this.Controls.Add(this.taskGroupBox);
this.Name = "PrimeNumberCalculatorMain";
this.Text = "Prime Number Calculator";
this.taskGroupBox.ResumeLayout(false);
this.buttonPanel.ResumeLayout(false);
this.ResumeLayout(false);
}
#endregion

[STAThread]
static void Main()
{
Application.Run(new PrimeNumberCalculatorMain());
}
}
/////////////////////////////////////////////////////////////
#region PrimeNumberCalculator Implementation

public delegate void ProgressChangedEventHandler(


ProgressChangedEventArgs e);

public delegate void CalculatePrimeCompletedEventHandler(


object sender, CalculatePrimeCompletedEventArgs e);

// This class implements the Event-based Asynchronous Pattern.


// It asynchronously computes whether a number is prime or
// composite (not prime).
public class PrimeNumberCalculator : Component
{
private delegate void WorkerEventHandler(int numberToCheck,
AsyncOperation asyncOp);
private SendOrPostCallback onProgressReportDelegate;
private SendOrPostCallback onCompletedDelegate;
private HybridDictionary userStateToLifetime =
new HybridDictionary();
private System.ComponentModel.Container components = null;

/////////////////////////////////////////////////////////////
#region Public events

public event ProgressChangedEventHandler ProgressChanged;


public event CalculatePrimeCompletedEventHandler
CalculatePrimeCompleted;

#endregion

MCT: Luis Dueas Pag 57 de 80


Modelos para la Programacin Asincrnica en .NET

/////////////////////////////////////////////////////////////
#region Construction and destruction

public PrimeNumberCalculator(IContainer container)


{
container.Add(this);
InitializeComponent();
InitializeDelegates();
}

public PrimeNumberCalculator()
{
InitializeComponent();
InitializeDelegates();
}

protected virtual void InitializeDelegates()


{
onProgressReportDelegate = new SendOrPostCallback(ReportProgress);
onCompletedDelegate = new SendOrPostCallback(CalculateCompleted);
}

protected override void Dispose(bool disposing)


{
if (disposing)
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose(disposing);
}

#endregion // Construction and destruction

/////////////////////////////////////////////////////////////
#region Implementation
// This method starts an asynchronous calculation.
// First, it checks the supplied task ID for uniqueness.
// If taskId is unique, it creates a new WorkerEventHandler
// and calls its BeginInvoke method to start the calculation.
public virtual void CalculatePrimeAsync(int numberToTest,
object taskId)
{
// Create an AsyncOperation for taskId.
AsyncOperation asyncOp =
AsyncOperationManager.CreateOperation(taskId);
// Multiple threads will access the task dictionary,
// so it must be locked to serialize access.
lock (userStateToLifetime.SyncRoot)
{
if (userStateToLifetime.Contains(taskId))
{
throw new ArgumentException(
"Task ID parameter must be unique", "taskId");
}
userStateToLifetime[taskId] = asyncOp;
}
// Start the asynchronous operation.
WorkerEventHandler workerDelegate = new
WorkerEventHandler(CalculateWorker);
workerDelegate.BeginInvoke(numberToTest, asyncOp, null, null);
}

// Utility method for determining if a task has been canceled.


private bool TaskCanceled(object taskId)

MCT: Luis Dueas Pag 58 de 80


Modelos para la Programacin Asincrnica en .NET

{
return( userStateToLifetime[taskId] == null );
}

// This method cancels a pending asynchronous operation.


public void CancelAsync(object taskId)
{
AsyncOperation asyncOp = userStateToLifetime[taskId] as
AsyncOperation;
if (asyncOp != null)
{
lock (userStateToLifetime.SyncRoot)
{
userStateToLifetime.Remove(taskId);
}
}
}

// This method performs the actual prime number computation.


// It is executed on the worker thread.
private void CalculateWorker(int numberToTest, AsyncOperation asyncOp)
{
bool isPrime = false;
int firstDivisor = 1;
Exception e = null;
// Check that the task is still active. The operation may have
// been canceled before the thread was scheduled.
if (!TaskCanceled(asyncOp.UserSuppliedState))
{
try
{
// Find all the prime numbers up to
// the square root of numberToTest.
ArrayList primes = BuildPrimeNumberList(
numberToTest, asyncOp);
// Now we have a list of primes less than numberToTest.
isPrime = IsPrime(primes, numberToTest, out firstDivisor);
}
catch (Exception ex)
{
e = ex;
}
}
//CalculatePrimeState calcState = new CalculatePrimeState(
// numberToTest,
// firstDivisor,
// isPrime,
// e,
// TaskCanceled(asyncOp.UserSuppliedState),
// asyncOp);
//this.CompletionMethod(calcState);
this.CompletionMethod(numberToTest, firstDivisor, isPrime, e,
TaskCanceled(asyncOp.UserSuppliedState), asyncOp);
//completionMethodDelegate(calcState);
}

// This method computes the list of prime numbers used by the


// IsPrime method.
private ArrayList BuildPrimeNumberList(
int numberToTest, AsyncOperation asyncOp)
{
ProgressChangedEventArgs e = null;
ArrayList primes = new ArrayList();
int firstDivisor;
int n = 5;
// Add the first prime numbers.
primes.Add(2);

MCT: Luis Dueas Pag 59 de 80


Modelos para la Programacin Asincrnica en .NET

primes.Add(3);
// Do the work.
while(n<numberToTest&&!TaskCanceled(asyncOp.UserSuppliedState))
{
if (IsPrime(primes, n, out firstDivisor))
{
// Report to the client that a prime was found.
e = new CalculatePrimeProgressChangedEventArgs(n,
(int)((float)n / (float)numberToTest * 100),
asyncOp.UserSuppliedState);
asyncOp.Post(this.onProgressReportDelegate, e);
primes.Add(n);
// Yield the rest of this time slice.
Thread.Sleep(0);
}
// Skip even numbers.
n += 2;
}
return primes;
}

// This method tests n for primality against the list of


// prime numbers contained in the primes parameter.
private bool IsPrime(ArrayList primes, int n, out int firstDivisor)
{
bool foundDivisor = false;
bool exceedsSquareRoot = false;
int i = 0;
int divisor = 0;
firstDivisor = 1;
// Stop the search if: there are no more primes in the list,
// there is a divisor of n in the list, or
// there is a prime that is larger than the square root of n.
while ((i < primes.Count) && !foundDivisor && !exceedsSquareRoot)
{
// The divisor variable will be the smallest
// prime number not yet tried.
divisor = (int)primes[i++];
// Determine whether the divisor is greater
// than the square root of n.
if (divisor * divisor > n)
{
exceedsSquareRoot = true;
}
// Determine whether the divisor is a factor of n.
else if (n % divisor == 0)
{
firstDivisor = divisor;
foundDivisor = true;
}
}
return !foundDivisor;
}

// This method is invoked via the AsyncOperation object,


// so it is guaranteed to be executed on the correct thread.
private void CalculateCompleted(object operationState)
{
CalculatePrimeCompletedEventArgs e =
operationState as CalculatePrimeCompletedEventArgs;
OnCalculatePrimeCompleted(e);
}

// This method is invoked via the AsyncOperation object,


// so it is guaranteed to be executed on the correct thread.
private void ReportProgress(object state)
{

MCT: Luis Dueas Pag 60 de 80


Modelos para la Programacin Asincrnica en .NET

ProgressChangedEventArgs e = state as ProgressChangedEventArgs;


OnProgressChanged(e);
}

protected void OnCalculatePrimeCompleted(


CalculatePrimeCompletedEventArgs e)
{
if (CalculatePrimeCompleted != null)
{
CalculatePrimeCompleted(this, e);
}
}

protected void OnProgressChanged(ProgressChangedEventArgs e)


{
if (ProgressChanged != null)
{
ProgressChanged(e);
}
}

// This is the method that the underlying, free-threaded asynchronous


// behavior will invoke. This will happen on an arbitrary thread.
private void CompletionMethod(int numberToTest, int firstDivisor,
bool isPrime, Exception exception, bool canceled,
AsyncOperation asyncOp)
{
// If the task was not previously canceled,
// remove the task from the lifetime collection.
if (!canceled)
{
lock (userStateToLifetime.SyncRoot)
{
userStateToLifetime.Remove(asyncOp.UserSuppliedState);
}
}
// Package the results of the operation in a
// CalculatePrimeCompletedEventArgs.
CalculatePrimeCompletedEventArgs e =
new CalculatePrimeCompletedEventArgs(
numberToTest, firstDivisor, isPrime, exception,
canceled, asyncOp.UserSuppliedState);
// End the task. The asyncOp object is responsible
// for marshaling the call.
asyncOp.PostOperationCompleted(onCompletedDelegate, e);
// Note that after the call to OperationCompleted,
// asyncOp is no longer usable, and any attempt to use it
// will cause an exception to be thrown.
}

#endregion

/////////////////////////////////////////////////////////////
#region Component Designer generated code

private void InitializeComponent()


{
components = new System.ComponentModel.Container();
}

#endregion
}

public class CalculatePrimeProgressChangedEventArgs :


ProgressChangedEventArgs
{
private int latestPrimeNumberValue = 1;

MCT: Luis Dueas Pag 61 de 80


Modelos para la Programacin Asincrnica en .NET

public CalculatePrimeProgressChangedEventArgs(int latestPrime,


int progressPercentage, object userToken) :
base(progressPercentage, userToken)
{
this.latestPrimeNumberValue = latestPrime;
}

public int LatestPrimeNumber


{
get
{
return latestPrimeNumberValue;
}
}
}

public class CalculatePrimeCompletedEventArgs : AsyncCompletedEventArgs


{
private int numberToTestValue = 0;
private int firstDivisorValue = 1;
private bool isPrimeValue;

public CalculatePrimeCompletedEventArgs(int numberToTest,


int firstDivisor, bool isPrime, Exception e, bool canceled,
object state) : base(e, canceled, state)
{
this.numberToTestValue = numberToTest;
this.firstDivisorValue = firstDivisor;
this.isPrimeValue = isPrime;
}

public int NumberToTest


{
get
{
// Raise an exception if the operation failed or was canceled.
RaiseExceptionIfNecessary();
// If the operation was successful, return the property value.
return numberToTestValue;
}
}

public int FirstDivisor


{
get
{
// Raise an exception if the operation failed or was canceled.
RaiseExceptionIfNecessary();
// If the operation was successful, return the property value.
return firstDivisorValue;
}
}

public bool IsPrime


{
get
{
// Raise an exception if the operation failed or was canceled.
RaiseExceptionIfNecessary();
// If the operation was successful, return the property value.
return isPrimeValue;
}
}
}
#endregion
}

MCT: Luis Dueas Pag 62 de 80


Modelos para la Programacin Asincrnica en .NET

2.1.6. Cmo: Utilizar componentes que admitan el modelo


asincrnico basado en eventos
Muchos componentes le ofrecen la opcin de realizar tareas de forma asincrnica. Los componentes SoundPlayer
y PictureBox, por ejemplo, le permiten cargar sonidos e imgenes "en segundo plano", mientras el subproceso principal
contina ejecutndose sin interrupciones.
El uso de mtodos asincrnicos en una clase que admite Informacin general sobre el modelo asincrnico basado en
eventos puede reducirse a asociar un controlador de eventos al evento nombreDeMtodoCompleted del componente, al
igual que hara con cualquier otro evento. Al llamar al mtodo nombreDeMtodoAsync, la aplicacin seguir
ejecutndose sin interrupciones, hasta que se produzca el evento nombreDeMtodoCompleted. Puede examinar el
parmetro AsyncCompletedEventArgs del controlador de eventos para determinar si la operacin asincrnica ha
finalizado correctamente o se ha cancelado.
El siguiente procedimiento muestra cmo utilizar la funcin de carga de imgenes asincrnica de un control PictureBox.

Para permitir que un control PictureBox cargue una imagen de forma asincrnica
1. Cree una instancia del componente PictureBox en el formulario.
2. Asigne un controlador de eventos al evento LoadCompleted.
Compruebe aqu que no se haya producido ningn error durante la descarga asincrnica. Aqu tambin puede
comprobar posibles cancelaciones.
public Form1()
{
InitializeComponent();
this.pictureBox1.LoadCompleted += new
System.ComponentModel.AsyncCompletedEventHandler(
this.pictureBox1_LoadCompleted);
}

private void pictureBox1_LoadCompleted(object sender,


AsyncCompletedEventArgs e)
{
if (e.Error != null)
{
MessageBox.Show(e.Error.Message, "Load Error");
}
else if (e.Cancelled)
{
MessageBox.Show("Load canceled", "Canceled");
}
else
{
MessageBox.Show("Load completed", "Completed");
}
}
3. Agregue dos botones al formulario, los botones denominados loadButton y cancelLoadButton. Agregue
controladores de eventos Click para iniciar y cancelar la descarga.
private void loadButton_Click(object sender, EventArgs e)
{
// Replace with a real url.
pictureBox1.LoadAsync("http://www.tailspintoys.com/image.jpg");
}

private void cancelLoadButton_Click(object sender, EventArgs e)


{
pictureBox1.CancelAsync();
}
4. Ejecute la aplicacin.
A medida que vaya descargndose la imagen, podr mover libremente el formulario, minimizarlo y
maximizarlo.

MCT: Luis Dueas Pag 63 de 80


Modelos para la Programacin Asincrnica en .NET

3. Modelo de programacin asincrnica (APM)


Una operacin asincrnica que utiliza el modelo de diseo IAsyncResult se implementa como dos mtodos
denominados BeginnombreDeOperacin y EndnombreDeOperacinque empiezan y terminan respectivamente la
operacin asincrnica nombreDeOperacin. Por ejemplo, la clase FileStream proporciona los mtodos BeginRead y
EndRead para leer de forma asincrnica bytes de un archivo. Estos mtodos implementan la versin asincrnica del
mtodo Read.
Despus de llamar a BeginnombreDeOperacin, una aplicacin puede seguir ejecutando instrucciones en el subproceso
que realiza la llamada mientras la operacin asincrnica se lleva a cabo en un subproceso diferente. Para cada llamada
a BeginnombreDeOperacin, la aplicacin tambin debera llamar a EndnombreDeOperacin para obtener los resultados
de la operacin.

Comenzar una operacin asincrnica


El mtodo BeginnombreDeOperacin inicia la operacin asincrnica nombreDeOperacin y devuelve un objeto que
implementa la interfaz IAsyncResult. Los objetos IAsyncResult almacenan informacin sobre una operacin
asincrnica. En la siguiente tabla se muestra informacin sobre una operacin asincrnica.

Miembro Descripcin

AsyncState Objeto especfico opcional de aplicacin que contiene informacin sobre la


operacin asincrnica.

AsyncWaitHandle WaitHandle que se puede utilizar para bloquear la ejecucin de la aplicacin


hasta que la operacin asincrnica finaliza.

CompletedSynchronously Valor que indica si la operacin asincrnica se complet en el subproceso


utilizado para llamar a BeginnombreDeOperacin en lugar de completarse en
un subproceso ThreadPool independiente.

IsCompleted Valor que indica si la operacin asincrnica ha finalizado.


Un mtodo BeginnombreDeOperacin toma todos los parmetros declarados en la firma de la versin sincrnica del
mtodo que se pasen por valor o por referencia. Los parmetros out no forman parte de la firma del
mtodo BeginnombreDeOperacin. La firma del mtodo BeginnombreDeOperacin tambin incluye dos parmetros
adicionales. El primero define un delegado AsyncCallback que hace referencia a un mtodo al que se llama cuando
finaliza la operacin asincrnica. El llamador puede especificar null (Nothing en Visual Basic) si no desea que se invoque
un mtodo cuando la operacin finaliza. El segundo parmetro adicional es un objeto definido por el usuario. Este
objeto se puede utilizar para pasar informacin de estado especfica de la aplicacin al mtodo invocado cuando la
operacin asincrnica finaliza. Si un mtodo BeginnombreDeOperacin toma parmetros adicionales especficos de
operacin, como una matriz de bytes para almacenar bytes ledos de un archivo, el objeto de estado de aplicacin
y AsyncCallback son los ltimos parmetros de la firma del mtodo BeginnombreDeOperacin.
Begin nombreDeOperacin devuelve inmediatamente el control al subproceso que realiza la llamada. Si el
mtodo BeginnombreDeOperacin produce excepciones, ser antes de que se inicie la operacin asincrnica. Si el
mtodo BeginnombreDeOperacin provoca excepciones, no se invoca el mtodo de devolucin de llamada.

Finalizar una operacin asincrnica


El mtodo EndnombreDeOperacin finaliza la operacin asincrnica nombreDeOperacin. El tipo del valor devuelto del
mtodo EndnombreDeOperacin coincide con el devuelto por su homlogo sincrnico y es especfico de la operacin
asincrnica. Por ejemplo, el mtodo EndRead devuelve el nmero de bytes ledos de FileStream y el mtodo
EndGetHostByName devuelve un objeto IPHostEntry que contiene informacin acerca de un equipo host. El
mtodo EndnombreDeOperacin toma cualquier parmetro out o ref declarado en la firma de la versin sincrnica del
mtodo. Adems de los parmetros del mtodo sincrnico, el mtodo EndnombreDeOperacintambin incluye un
parmetro IAsyncResult. Los llamadores deben pasar la instancia devuelta por la llamada correspondiente a Begin
nombreDeOperacin.
Si la operacin asincrnica representada por el objeto IAsyncResult no se ha completado cuando se llama a End
nombreDeOperacin, EndnombreDeOperacin bloquea el subproceso que realiza la llamada hasta que se completa la
operacin asincrnica. Las excepciones generadas por la operacin asincrnica se producen desde el
mtodoEndnombreDeOperacin. No se ha definido el efecto de llamar varias veces al mtodo Endnombre
DeOperacin con el mismo objeto IAsyncResult. Tampoco se ha definido la llamada al mtodo Endnombre
DeOperacin con un objeto IAsyncResult que no fue devuelto por el mtodo Begin relacionado.

Nota

MCT: Luis Dueas Pag 64 de 80


Modelos para la Programacin Asincrnica en .NET

Para cualquiera de los escenarios indefinidos, los implementadores deberan considerar la posibilidad de
producir InvalidOperationException.

Nota

Los implementadores de este modelo de diseo deben avisar al llamador de que la operacin sincrnica ha
finalizado estableciendo IsCompleted en True, llamando al mtodo de devolucin de llamada asincrnico (si se
ha especificado) y sealizando el objeto AsyncWaitHandle.
Los desarrolladores de aplicaciones disponen de varias opciones de diseo para obtener acceso a los resultados de la
operacin asincrnica. La opcin correcta depende de si la aplicacin tiene instrucciones que se pueden ejecutar
mientras la operacin finaliza. Si una aplicacin no puede realizar ningn otro trabajo hasta que reciba los resultados de
la operacin asincrnica, debe bloquearse hasta que los resultados estn disponibles. Para establecer el bloqueo hasta
que finalice una operacin asincrnica, puede recurrir a uno de los mtodos siguientes:
Llamar a EndnombreDeOperacin desde el subproceso principal de la aplicacin, lo que supone bloquear la
ejecucin de la aplicacin hasta que la operacin se complete.
Utilice el objeto AsyncWaitHandle para bloquear la ejecucin de la aplicacin hasta que una o ms
operaciones hayan finalizado.
En el caso de las aplicaciones que no necesariamente deben bloquearse mientras la operacin asincrnica finaliza,
puede recurrir a uno de los mtodos siguientes:
Sondear el estado de ejecucin de la operacin mediante la comprobacin peridica de la
propiedad IsCompleted y la realizacin de una llamada aEndnombreDeOperacin cuando se complete la
operacin.
Utilice un delegado AsyncCallback para especificar que se invoque un mtodo cuando finalice la operacin.

3.1. Llamar a mtodos asincrnicos mediante IAsyncResult


Los tipos de las bibliotecas de clases de otros fabricantes y de .NET Framework pueden proporcionar mtodos que
permitan que una aplicacin determinada siga ejecutndose mientras se llevan a cabo operaciones asincrnicas en
subprocesos diferentes con respecto al subproceso de la aplicacin principal. En las siguientes secciones se describen y
se proporcionan ejemplos de cdigo que muestran las diferentes maneras en las que es posible llamar a mtodos
asincrnicos que utilicen el modelo de diseo IAsyncResult.

3.1.1. Bloquear la ejecucin de una aplicacin mediante


AsyncWaitHandle
Las aplicaciones que no pueden seguir realizando otra tarea mientras esperan los resultados de una operacin
asincrnica deben bloquearse hasta que la operacin finalice.Utilice una de las opciones siguientes para bloquear el
subproceso principal de la aplicacin en cuestin a la espera de que una operacin asincrnica finalice:
Utilice la propiedad AsyncWaitHandle de la interfaz IAsyncResult devuelta por el mtodo Beginnombre
DeOperacin de la operacin asincrnica. Este tema muestra la ejecucin de dicho procedimiento.
Llame al mtodo EndnombreDeOperacin de la operacin asincrnica.
Las aplicaciones que utilizan uno o varios objetos WaitHandle para establecer bloqueos hasta que se completa una
operacin asincrnica suelen llamar al mtodoBeginnombreDeOperacin, realizan todas las tareas que se puedan
realizar sin tener los resultados de dicha operacin y, a continuacin, se bloquean hasta que se completa la operacin
asincrnica. Una aplicacin se puede bloquear en una nica operacin llamando a uno de los mtodos
WaitOne mediante AsyncWaitHandle. Para bloquear la ejecucin de una aplicacin mientras espera que finalice un
conjunto de operaciones asincrnicas, almacene los objetos AsyncWaitHandle asociados en una matriz y llame a uno de
los mtodos WaitAll. Para bloquear la ejecucin de una aplicacin mientras espera que finalice alguno de los conjuntos
de operaciones asincrnicas, almacene los objetos AsyncWaitHandle asociados en una matriz y llame a uno de los
mtodos WaitAny.

Ejemplo
El ejemplo de cdigo siguiente muestra cmo utilizar los mtodos asincrnicos en la clase DNS para recuperar
informacin sobre el Sistema de nombres de dominio para un equipo especificado por el usuario. El ejemplo muestra
cmo realizar el bloqueo utilizando el objeto WaitHandle asociado a la operacin asincrnica. Observe que se pasa el
valor null (Nothing en Visual Basic) para los parmetros BeginGetHostByName, requestCallback y stateObject, puesto
que no son necesarios cuando se utiliza este procedimiento.
/*The following example demonstrates using asynchronous methods to
get Domain Name System information for the specified host computer.*/
using System;

MCT: Luis Dueas Pag 65 de 80


Modelos para la Programacin Asincrnica en .NET

using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace Examples.AdvancedProgramming.AsynchronousOperations
{
public class WaitUntilOperationCompletes
{
public static void Main(string[] args)
{
// Make sure the caller supplied a host name.
if (args.Length == 0 || args[0].Length == 0)
{
// Print a message and exit.
Console.WriteLine("You must specify the name of a host
computer.");
return;
}
// Start the asynchronous request for DNS information.
IAsyncResult result = Dns.BeginGetHostEntry(args[0], null, null);
Console.WriteLine("Processing request for information...");
// Wait until the operation completes.
result.AsyncWaitHandle.WaitOne();
// The operation completed. Process the results.
try
{
// Get the results.
IPHostEntry host = Dns.EndGetHostEntry(result);
string[] aliases = host.Aliases;
IPAddress[] addresses = host.AddressList;
if (aliases.Length > 0)
{
Console.WriteLine("Aliases");
for (int i = 0; i < aliases.Length; i++)
{
Console.WriteLine("{0}", aliases[i]);
}
}
if (addresses.Length > 0)
{
Console.WriteLine("Addresses");
for (int i = 0; i < addresses.Length; i++)
{
Console.WriteLine("{0}",addresses[i].ToString());
}
}
}
catch (SocketException e)
{
Console.WriteLine("Exception occurred while processing the
request: {0}", e.Message);
}
}
}
}

3.1.2. Bloquear la ejecucin de una aplicacin al finalizar una


operacin asincrnica
Las aplicaciones que no pueden seguir realizando otra tarea mientras esperan los resultados de una operacin
asincrnica deben bloquearse hasta que la operacin finalice.Utilice una de las opciones siguientes para bloquear el
subproceso principal de la aplicacin en cuestin a la espera de que una operacin asincrnica finalice:
Llame al mtodo EndnombreDeOperacin de la operacin asincrnica. Este tema muestra la ejecucin de
dicho procedimiento.
Utilice la propiedad AsyncWaitHandle de la interfaz IAsyncResult devuelta por el mtodo Beginnombre
DeOperacin de la operacin asincrnica.

MCT: Luis Dueas Pag 66 de 80


Modelos para la Programacin Asincrnica en .NET

Las aplicaciones que utilizan el mtodo EndnombreDeOperacin para establecer bloqueos hasta que se completa una
operacin asincrnica normalmente llamarn al mtodoBeginnombreDeOperacin, realizarn cualquier tarea que se
pueda sin los resultados de la operacin y, a continuacin, llamarn a EndnombreDeOperacin.

Ejemplo
El siguiente ejemplo de cdigo muestra cmo utilizar los mtodos asincrnicos en la clase Dns con el fin de recuperar
informacin sobre el Sistema de nombres de dominio para un equipo especificado por el usuario. Hay que observar que
se pasa null (Nothing en Visual Basic) para los parmetros BeginGetHostByNamerequestCallback y stateObject ya que
estos argumentos no son necesarios a la hora de utilizar este enfoque.
/* The following example demonstrates using asynchronous methods to
get Domain Name System information for the specified host computer.*/
using System;
using System.Net;
using System.Net.Sockets;

namespace Examples.AdvancedProgramming.AsynchronousOperations
{
public class BlockUntilOperationCompletes
{
public static void Main(string[] args)
{
// Make sure the caller supplied a host name.
if (args.Length == 0 || args[0].Length == 0)
{
// Print a message and exit.
Console.WriteLine("You must specify the name of a host
computer.");
return;
}
// Start the asynchronous request for DNS information.
// This example does not use a delegate or user-supplied object
// so the last two arguments are null.
IAsyncResult result = Dns.BeginGetHostEntry(args[0], null, null);
Console.WriteLine("Processing your request for information...");
// Do any additional work that can be done here.
try
{
// EndGetHostByName blocks until the process completes.
IPHostEntry host = Dns.EndGetHostEntry(result);
string[] aliases = host.Aliases;
IPAddress[] addresses = host.AddressList;
if (aliases.Length > 0)
{
Console.WriteLine("Aliases");
for (int i = 0; i < aliases.Length; i++)
{
Console.WriteLine("{0}", aliases[i]);
}
}
if (addresses.Length > 0)
{
Console.WriteLine("Addresses");
for (int i = 0; i < addresses.Length; i++)
{
Console.WriteLine("{0}",addresses[i].ToString());
}
}
}
catch (SocketException e)
{
Console.WriteLine("An exception occurred while processing the
request: {0}", e.Message);
}
}
}
}

MCT: Luis Dueas Pag 67 de 80


Modelos para la Programacin Asincrnica en .NET

3.1.3. Sondear el estado de una operacin asincrnica


Aquellas aplicaciones que pueden realizar otra tarea mientras esperan los resultados de una operacin asincrnica, no
se deberan bloquear mientras esperan a que la operacin finalice. Utilice una de las siguientes opciones para seguir
ejecutando instrucciones mientras se est a la espera de que una operacin asincrnica finalice:
Utilice la propiedad IsCompleted de la interfaz IAsyncResult devuelta por el mtodo Beginnombre
DeOperacin de la operacin asincrnica para determinar si la operacin se ha completado. Este enfoque se
conoce como sondeo y se muestra en este tema.
Utilice un delegado AsyncCallback para procesar los resultados de la operacin asincrnica en un subproceso
independiente.

Ejemplo
El siguiente ejemplo de cdigo muestra cmo utilizar los mtodos asincrnicos en la clase Dns con el fin de recuperar
informacin sobre el Sistema de nombres de dominio para un equipo especificado por el usuario. Este ejemplo inicia la
operacin asincrnica y, a continuacin, imprime puntos (".") en la consola hasta que la operacin se haya
completado. Hay que observar que se pasa null (Nothing en Visual Basic) para los parmetros BeginGetHostByName
AsyncCallback y Object ya que estos argumentos no son necesarios a la hora de utilizar este enfoque.
/*The following example demonstrates using asynchronous methods to
get Domain Name System information for the specified host computer.
This example polls to detect the end of the asynchronous operation.*/
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace Examples.AdvancedProgramming.AsynchronousOperations
{
public class PollUntilOperationCompletes
{
static void UpdateUserInterface()
{
// Print a period to indicate that the application
// is still working on the request.
Console.Write(".");
}
public static void Main(string[] args)
{
// Make sure the caller supplied a host name.
if (args.Length == 0 || args[0].Length == 0)
{
// Print a message and exit.
Console.WriteLine("You must specify the name of a host
computer.");
return;
}
// Start the asychronous request for DNS information.
IAsyncResult result = Dns.BeginGetHostEntry(args[0], null, null);
Console.WriteLine("Processing request for information...");
// Poll for completion information.
// Print periods (".") until the operation completes.
while (result.IsCompleted != true)
{
UpdateUserInterface();
}
// The operation is complete. Process the results.
// Print a new line.
Console.WriteLine();
try
{
IPHostEntry host = Dns.EndGetHostEntry(result);
string[] aliases = host.Aliases;
IPAddress[] addresses = host.AddressList;
if (aliases.Length > 0)
{
Console.WriteLine("Aliases");

MCT: Luis Dueas Pag 68 de 80


Modelos para la Programacin Asincrnica en .NET

for (int i = 0; i < aliases.Length; i++)


{
Console.WriteLine("{0}", aliases[i]);
}
}
if (addresses.Length > 0)
{
Console.WriteLine("Addresses");
for (int i = 0; i < addresses.Length; i++)
{
Console.WriteLine("{0}",addresses[i].ToString());
}
}
}
catch (SocketException e)
{
Console.WriteLine("An exception occurred while processing the
request: {0}", e.Message);
}
}
}
}

3.1.4. Utilizar un delegado AsyncCallback para finalizar una


operacin asincrnica
Aquellas aplicaciones que pueden realizar otra tarea mientras esperan los resultados de una operacin asincrnica, no
se deberan bloquear mientras esperan a que la operacin finalice. Utilice una de las siguientes opciones para seguir
ejecutando instrucciones mientras se est a la espera de que una operacin asincrnica finalice:
Utilice un delegado AsyncCallback para procesar los resultados de la operacin asincrnica en un subproceso
independiente. Este tema muestra la ejecucin de dicho procedimiento.
Utilice la propiedad IsCompleted de la interfaz IAsyncResult devuelta por el mtodo Beginnombre
DeOperacin de la operacin asincrnica para determinar si la operacin se ha completado.

Ejemplo
El siguiente ejemplo de cdigo muestra cmo utilizar los mtodos asincrnicos en la clase Dns con el fin de recuperar
informacin sobre el Sistema de nombres de dominio (DNS) para equipos especificados por el usuario. En este ejemplo,
se crea un delegado AsyncCallback que hace referencia el mtodo ProcessDnsInformation. Se llama a este mtodo
una vez por cada solicitud asincrnica con el fin de obtener informacin DNS.
Obsrvese que el host especificado por el usuario se pasa al parmetro BeginGetHostByNameObject.
/*The following example demonstrates using asynchronous methods to get Domain
Name System information for the specified host computers. This example uses a
delegate to obtain the results of each asynchronous operation.*/
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Collections.Specialized;
using System.Collections;

namespace Examples.AdvancedProgramming.AsynchronousOperations
{
public class UseDelegateForAsyncCallback
{
static int requestCounter;
static ArrayList hostData = new ArrayList();
static StringCollection hostNames = new StringCollection();
static void UpdateUserInterface()
{
// Print a message to indicate that the application
// is still working on the remaining requests.
Console.WriteLine("{0} requests remaining.", requestCounter);
}
public static void Main()

MCT: Luis Dueas Pag 69 de 80


Modelos para la Programacin Asincrnica en .NET

{
// Create the delegate that will process the results of the
// asynchronous request.
AsyncCallback callBack = new AsyncCallback(ProcessDnsInformation);
string host;
do
{
Console.Write(" Enter the name of a host computer or <enter>
to finish: ");
host = Console.ReadLine();
if (host.Length > 0)
{
// Increment the request counter in a thread safe manner.
Interlocked.Increment(ref requestCounter);
// Start the asynchronous request for DNS information.
Dns.BeginGetHostEntry(host, callBack, host);
}
} while (host.Length > 0);
// The user has entered all of the host names for lookup.
// Now wait until the threads complete.
while (requestCounter > 0)
{
UpdateUserInterface();
}
// Display the results.
for (int i = 0; i< hostNames.Count; i++)
{
object data = hostData [i];
string message = data as string;
// A SocketException was thrown.
if (message != null)
{
Console.WriteLine("Request for {0} returned message: {1}",
hostNames[i], message);
continue;
}
// Get the results.
IPHostEntry h = (IPHostEntry) data;
string[] aliases = h.Aliases;
IPAddress[] addresses = h.AddressList;
if (aliases.Length > 0)
{
Console.WriteLine("Aliases for {0}", hostNames[i]);
for (int j = 0; j < aliases.Length; j++)
{
Console.WriteLine("{0}", aliases[j]);
}
}
if (addresses.Length > 0)
{
Console.WriteLine("Addresses for {0}", hostNames[i]);
for (int k = 0; k < addresses.Length; k++)
{
Console.WriteLine("{0}",addresses[k].ToString());
}
}
}
}

//The following method is called when each asynchronous operation completes


static void ProcessDnsInformation(IAsyncResult result)
{
string hostName = (string) result.AsyncState;
hostNames.Add(hostName);
try
{
// Get the results.

MCT: Luis Dueas Pag 70 de 80


Modelos para la Programacin Asincrnica en .NET

IPHostEntry host = Dns.EndGetHostEntry(result);


hostData.Add(host);
}
// Store the exception message.
catch (SocketException e)
{
hostData.Add(e.Message);
}
finally
{
// Decrement the request counter in a thread-safe manner.
Interlocked.Decrement(ref requestCounter);
}
}
}
}

3.1.4.1. Utilizar un delegado AsyncCallback y un objeto State


Cuando se utiliza un delegado de AsyncCallback para procesar los resultados de la operacin asincrnica en un
subproceso independiente, se puede utilizar un objeto de estado para pasar informacin entre las devoluciones de
llamada y recuperar un resultado final. Este tema ilustra esta prctica con la expansin del ejemplo en Utilizar un
delegado AsyncCallback para finalizar una operacin asincrnica.

Ejemplo
El siguiente ejemplo de cdigo muestra cmo utilizar los mtodos asincrnicos en la clase Dns con el fin de recuperar
informacin sobre el Sistema de nombres de dominio (DNS) para equipos especificados por el usuario. Este ejemplo
define y utiliza la clase HostRequest para almacenar informacin de estado. Un objeto HostRequest se crea para cada
nombre de equipo escrito por el usuario. Este objeto se pasa al mtodo BeginGetHostByName. Se llama al mtodo
ProcessDnsInformation cada vez que una solicitud finaliza. El objeto HostRequest se recupera utilizando la
propiedad AsyncState. El mtodo ProcessDnsInformation utiliza el objeto HostRequest para almacenar un IPHost
Entry devuelto por la solicitud o un SocketException producido por la solicitud. Una vez que todas las solicitudes han
finalizado, la aplicacin recorre en iteracin los objetos HostRequest y muestra la informacin DNS o mensaje de
error SocketException.
/*The following example demonstrates using asynchronous methods to
get Domain Name System information for the specified host computer.*/
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Collections;

namespace Examples.AdvancedProgramming.AsynchronousOperations
{
// Create a state object that holds each requested host name,
// an associated IPHostEntry object or a SocketException.
public class HostRequest
{
// Stores the requested host name.
private string hostName;
// Stores any SocketException returned by the Dns EndGetHostByName
// method.
private SocketException e;
// Stores an IPHostEntry returned by the Dns EndGetHostByName method.
private IPHostEntry entry;

public HostRequest(string name)


{
hostName = name;
}

public string HostName


{
get
{

MCT: Luis Dueas Pag 71 de 80


Modelos para la Programacin Asincrnica en .NET

return hostName;
}
}

public SocketException ExceptionObject


{
get
{
return e;
}
set
{
e = value;
}
}

public IPHostEntry HostEntry


{
get
{
return entry;
}
set
{
entry = value;
}
}
}

public class UseDelegateAndStateForAsyncCallback


{
// The number of pending requests.
static int requestCounter;
static ArrayList hostData = new ArrayList();
static void UpdateUserInterface()
{
// Print a message to indicate that the application
// is still working on the remaining requests.
Console.WriteLine("{0} requests remaining.", requestCounter);
}
public static void Main()
{
// Create the delegate that will process the results of the
// asynchronous request.
AsyncCallback callBack = new AsyncCallback(ProcessDnsInformation);
string host;
do
{
Console.Write(" Enter the name of a host computer or <enter>
to finish: ");
host = Console.ReadLine();
if (host.Length > 0)
{
// Increment the request counter in a thread safe manner.
Interlocked.Increment(ref requestCounter);
// Create and store the state object for this request.
HostRequest request = new HostRequest(host);
hostData.Add(request);
// Start the asynchronous request for DNS information.
Dns.BeginGetHostEntry(host, callBack, request);
}
} while (host.Length > 0);
// The user has entered all of the host names for lookup.
// Now wait until the threads complete.
while (requestCounter > 0)
{
UpdateUserInterface();

MCT: Luis Dueas Pag 72 de 80


Modelos para la Programacin Asincrnica en .NET

}
// Display the results.
foreach(HostRequest r in hostData)
{
if (r.ExceptionObject != null)
{
Console.WriteLine("Request for host {0} returned the
following error: {1}.", r.HostName,
r.ExceptionObject.Message);
}
else
{
// Get the results.
IPHostEntry h = r.HostEntry;
string[] aliases = h.Aliases;
IPAddress[] addresses = h.AddressList;
if (aliases.Length > 0)
{
Console.WriteLine("Aliases for {0}", r.HostName);
for (int j = 0; j < aliases.Length; j++)
{
Console.WriteLine("{0}", aliases[j]);
}
}
if (addresses.Length > 0)
{
Console.WriteLine("Addresses for {0}",r.HostName);
for (int k = 0; k < addresses.Length; k++)
{
Console.WriteLine("{0}",addresses[k].
ToString());
}
}
}
}
}

// The following method is invoked when each asynchronous operation


// completes.
static void ProcessDnsInformation(IAsyncResult result)
{
// Get the state object associated with this request.
HostRequest request = (HostRequest) result.AsyncState;
try
{
// Get the results and store them in the state object.
IPHostEntry host = Dns.EndGetHostEntry(result);
request.HostEntry = host;
}
catch (SocketException e)
{
// Store any SocketExceptions.
request.ExceptionObject = e;
}
finally
{
// Decrement the request counter in a thread-safe manner.
Interlocked.Decrement(ref requestCounter);
}
}
}
}

3.2. Programacin asincrnica mediante delegados


Los delegados permiten llamar a mtodos sincrnicos de forma asincrnica. Cuando se llama a un delegado
sincrnicamente, el mtodo Invoke llama al mtodo de destino directamente en el subproceso actual. Si se llama al

MCT: Luis Dueas Pag 73 de 80


Modelos para la Programacin Asincrnica en .NET

mtodo BeginInvoke, Common Language Runtime (CLR) coloca en cola la solicitud y devuelve inmediatamente el
control al elemento que lo llam. El mtodo de destino se llama asincrnicamente desde un subproceso del grupo de
subprocesos. El subproceso original, que envi la solicitud, puede continuar ejecutndose en paralelo con el mtodo de
destino. Si se ha especificado un mtodo de devolucin de llamada en la llamada al mtodo BeginInvoke, el mtodo de
devolucin de llamada se llama cuando finaliza el mtodo de destino. En el mtodo de devolucin de llamada, el
mtodo EndInvoke obtiene el valor devuelto y cualquier parmetro de entrada y salida o de solo salida. Si no se
especifica ningn mtodo de devolucin de llamada al llamar a BeginInvoke, se puede llamar a EndInvokedesde el
subproceso que llam a BeginInvoke.

Importante

Los compiladores deben emitir clases de delegados con los mtodos Invoke, BeginInvoke y EndInvoke
mediante la firma de delegados que especifique el usuario. Los mtodos EndInvoke y BeginInvoke deben
representarse como nativos. Debido a que estos mtodos estn marcados como nativos, Common Language
Runtime proporciona automticamente la implementacin en el momento en que se carga la clase. El cargador
se asegura de que no se reemplazarn.

3.2.1. Llamar a mtodos sincrnicos de forma asincrnica


.NET Framework permite llamar a cualquier mtodo de forma asincrnica. Para ello, es necesario que defina un delegado
con la misma firma que el mtodo al que desea llamar. Common Language Runtime definir automticamente los
mtodos BeginInvoke y EndInvoke para este delegado, con las firmas adecuadas.

Nota

Las llamadas de delegado asincrnicas, especficamente los mtodos EndInvoke y BeginInvoke, no se


admiten en .NET Compact Framework.
El mtodo BeginInvoke inicia la llamada asincrnica. Tiene los mismos parmetros que el mtodo que desea ejecutar
de forma asincrnica, ms dos parmetros opcionales adicionales. El primer parmetro es un
delegado AsyncCallback que hace referencia a un mtodo que se habr de llamar cuando finalice la llamada
asincrnica. El segundo parmetro es un objeto definido por el usuario que pasa informacin al mtodo de devolucin
de llamada. BeginInvoke vuelve inmediatamente y no espera que se complete la llamada asincrnica. BeginInvoke
devuelve IAsyncResult, que se puede usar para supervisar el progreso de la llamada asincrnica.
El mtodo EndInvoke recupera los resultados de la llamada asincrnica. Se puede llamar en cualquier momento
despus de ejecutar BeginInvoke. Si la llamada asincrnica no ha completado, EndInvoke bloquea el subproceso que
realiza la llamada hasta que se completa. Los parmetros EndInvoke se incluyen out y ref (<Fuera> ByRef y ByRef en
Visual Basic) del mtodo que desea ejecutar de forma asincrnica, ms IAsyncResult devuelto por BeginInvoke.

Nota

La caracterstica IntelliSense en Visual Studio 2005 muestra los parmetros de BeginInvoke y EndInvoke. Si
no est utilizando Visual Studio u otra herramienta similar, o si est utilizando C# con Visual Studio 2005,
consulte Modelo de programacin asincrnica (APM), donde encontrar una descripcin de los parmetros
definidos para estos mtodos.
En los ejemplos de cdigo de este tema se muestran cuatro de las formas ms comunes de utilizar los
mtodos BeginInvoke y EndInvoke para realizar llamadas asincrnicas. Despus de llamar a BeginInvoke, puede hacer
lo siguiente:
Realizar algunas operaciones y, a continuacin, llamar al mtodo EndInvoke para que mantenga un bloqueo
hasta que se complete la llamada.
Obtener un objeto WaitHandle mediante la propiedad IAsyncResult.AsyncWaitHandle, utilizar su mtodo
WaitOne para bloquear la ejecucin hasta que se sealice WaitHandle y, a continuacin, llamar al mtodo
EndInvoke.
Sondear el resultado IAsyncResult devuelto por BeginInvoke para determinar cundo se completa la llamada
asincrnica y, a continuacin, llamar al mtodoEndInvoke.
Pasar un delegado de un mtodo de devolucin de llamada a BeginInvoke. El mtodo se ejecuta en un
subproceso ThreadPool una vez finalizada la llamada asincrnica. El mtodo de devolucin de llamada llama
a EndInvoke.

Importante

Con independencia de la tcnica que utilice, llame siempre a EndInvoke para completar la llamada asincrnica.

Definir el mtodo Test y el delegado asincrnico

MCT: Luis Dueas Pag 74 de 80


Modelos para la Programacin Asincrnica en .NET

En los ejemplos de cdigo siguientes se muestran distintas maneras de llamar al mismo mtodo de ejecucin
prolongada, TestMethod, de forma asincrnica. El mtodoTestMethod muestra un mensaje en la consola para indicar
que ha comenzado el procesamiento, espera unos segundos y, a continuacin, finaliza. TestMethod tiene un
parmetro out para mostrar la manera en que esos parmetros se agregan a las firmas de BeginInvoke y
EndInvoke. Los parmetros ref se pueden controlar de manera similar.
En el ejemplo de cdigo siguiente se muestra la definicin de TestMethod y el delegado denominado
AsyncMethodCaller que se puede utilizar para llamar a TestMethodde forma asincrnica. Para compilar cualquiera de
los ejemplos de cdigo, debe incluir las definiciones del mtodo TestMethod y el delegado AsyncMethodCaller.
using System;
using System.Threading;

namespace Examples.AdvancedProgramming.AsynchronousOperations
{
public class AsyncDemo
{
// The method to be executed asynchronously.
public string TestMethod(int callDuration, out int threadId)
{
Console.WriteLine("Test method begins.");
Thread.Sleep(callDuration);
threadId = Thread.CurrentThread.ManagedThreadId;
return String.Format("My call time was {0}.",
callDuration.ToString());
}
}
// The delegate must have the same signature as the method
// it will call asynchronously.
public delegate string AsyncMethodCaller(int callDuration,
out int threadId);
}

Esperar una llamada asincrnica con EndInvoke


La manera ms sencilla de ejecutar un mtodo de forma asincrnica es empezar a ejecutar el mtodo llamando al
mtodo BeginInvoke del delegado, hacer algn trabajo en el subproceso principal y, a continuacin, llamar al
mtodo EndInvoke del delegado. EndInvoke podran bloquear el subproceso que realiza la llamada porque no vuelve
hasta que no se completa la llamada asincrnica. sta es una buena tcnica para utilizarla con operaciones de archivos o
red.

Importante

Dado que EndInvoke podra mantener un bloqueo, nunca debe llamar a este mtodo desde los subprocesos que dan
servicio a la interfaz de usuario.
using System;
using System.Threading;

namespace Examples.AdvancedProgramming.AsynchronousOperations
{
public class AsyncMain
{
public static void Main()
{
// The asynchronous method puts the thread id here.
int threadId;
// Create an instance of the test class.
AsyncDemo ad = new AsyncDemo();
// Create the delegate.
AsyncMethodCaller caller = new AsyncMethodCaller(ad.TestMethod);
// Initiate the asychronous call.
IAsyncResult result = caller.BeginInvoke(3000, out threadId, null,
null);
Thread.Sleep(0);
Console.WriteLine("Main thread {0} does some work.",
Thread.CurrentThread.ManagedThreadId);
// Call EndInvoke to wait for the asynchronous call to complete,
// and to retrieve the results.

MCT: Luis Dueas Pag 75 de 80


Modelos para la Programacin Asincrnica en .NET

string returnValue = caller.EndInvoke(out threadId, result);


Console.WriteLine("The call executed on thread {0}, with return
value \"{1}\".", threadId, returnValue);
}
}
}

/* This example produces output similar to the following:


Main thread 1 does some work. Test method begins.
The call executed on thread 3, with return value "My call time was 3000.".*/

Esperar una llamada asincrnica con WaitHandle


Puede obtener un objeto WaitHandle utilizando la propiedad AsyncWaitHandle de IAsyncResult devuelta por
BeginInvoke. WaitHandle se sealiza cuando finaliza la llamada asincrnica y puede esperar a que termine llamando al
mtodo WaitOne.
Si utiliza un objeto WaitHandle, puede realizar otros procesamientos adicionales antes o despus de que se complete la
llamada asincrnica, pero antes de llamar al mtodo EndInvoke para recuperar los resultados.

Nota

El identificador de espera no se cierra automticamente cuando llama a EndInvoke. Si libera todas las referencias
al identificador de espera, se liberarn los recursos del sistema cuando la recoleccin de elementos no utilizados
reclame el identificador de espera. Para liberar los recursos del sistema tan pronto como se deje de utilizar el
identificador de espera, elimnelo llamando al mtodo WaitHandle.Close. La recoleccin de elementos no
utilizados funciona ms eficazmente cuando los objetos descartables se eliminan de forma explcita.
using System;
using System.Threading;

namespace Examples.AdvancedProgramming.AsynchronousOperations
{
public class AsyncMain
{
static void Main()
{
// The asynchronous method puts the thread id here.
int threadId;
// Create an instance of the test class.
AsyncDemo ad = new AsyncDemo();
// Create the delegate.
AsyncMethodCaller caller = new AsyncMethodCaller(ad.TestMethod);
// Initiate the asychronous call.
IAsyncResult result = caller.BeginInvoke(3000, out threadId, null,
null);
Thread.Sleep(0);
Console.WriteLine("Main thread {0} does some work.",
Thread.CurrentThread.ManagedThreadId);
// Wait for the WaitHandle to become signaled.
result.AsyncWaitHandle.WaitOne();
// Perform additional processing here.
// Call EndInvoke to retrieve the results.
string returnValue = caller.EndInvoke(out threadId, result);
// Close the wait handle.
result.AsyncWaitHandle.Close();
Console.WriteLine("The call executed on thread {0}, with return
value \"{1}\".", threadId, returnValue);
}
}
}

/* This example produces output similar to the following:


Main thread 1 does some work. Test method begins.
The call executed on thread 3, with return value "My call time was 3000.".*/

Sondear la finalizacin de una llamada asincrnica

MCT: Luis Dueas Pag 76 de 80


Modelos para la Programacin Asincrnica en .NET

Puede utilizar la propiedad IsCompleted del objeto IAsyncResult devuelto por BeginInvoke para detectar el momento
en que se completa la llamada asincrnica. Puede hacer esto ltimo cuando realice la llamada asincrnica desde un
subproceso que d servicio a la interfaz de usuario. Sondear la finalizacin de una llamada asincrnica permite al
subproceso de llamada seguirse ejecutando mientras la llamada asincrnica se ejecuta en un subproceso ThreadPool.
using System;
using System.Threading;

namespace Examples.AdvancedProgramming.AsynchronousOperations
{
public class AsyncMain
{
static void Main() {
// The asynchronous method puts the thread id here.
int threadId;
// Create an instance of the test class.
AsyncDemo ad = new AsyncDemo();
// Create the delegate.
AsyncMethodCaller caller = new AsyncMethodCaller(ad.TestMethod);
// Initiate the asychronous call.
IAsyncResult result = caller.BeginInvoke(3000, out threadId, null,
null);
// Poll while simulating work.
while(result.IsCompleted == false) {
Thread.Sleep(250);
Console.Write(".");
}
// Call EndInvoke to retrieve the results.
string returnValue = caller.EndInvoke(out threadId, result);
Console.WriteLine("\nThe call executed on thread {0}, with return
value \"{1}\".", threadId, returnValue);
}
}
}

/* This example produces output similar to the following:


Test method begins. .............
The call executed on thread 3, with return value "My call time was 3000.".*/

Ejecutar un mtodo de devolucin de llamada cuando finaliza una llamada


asincrnica
Si no es necesario que el subproceso que inicia la llamada asincrnica sea el mismo que procesa los resultados, puede
ejecutar un mtodo de devolucin de llamada cuando se complete la llamada. El mtodo de devolucin de llamada se
ejecuta en un subproceso ThreadPool.
Para utilizar un mtodo de devolucin de llamada, debe pasar al mtodo BeginInvoke un delegado AsyncCallback que
represente al mtodo de devolucin de llamada.Tambin puede pasar un objeto que contenga la informacin que va a
utilizar el mtodo de devolucin de llamada. En el mtodo de devolucin de llamada, puede convertir IAsyncResult, que
es el nico parmetro del mtodo de devolucin de llamada, en un objeto AsyncResult. A continuacin, puede utilizar la
propiedad AsyncResult.AsyncDelegate para obtener el delegado que se utiliz para iniciar la llamada y, de ese modo,
pueda llamar a EndInvoke.
Notas sobre el ejemplo:
El parmetro threadId de TestMethod es un parmetro out (<Fuera> ByRef en Visual Basic), por lo que el
valor de entrada TestMethod nunca utiliza. Una variable ficticia se pasa a la llamada a BeginInvoke. Si el
parmetro threadId fuera un parmetro ref (ByRef en Visual Basic), la variable tendra que ser un campo de
nivel de clase para que pudiera pasarse a los mtodos BeginInvoke y EndInvoke.
La informacin de estado que se pasa a BeginInvoke es una cadena de formato, que el mtodo de
devolucin de llamada utiliza para dar formato a un mensaje de salida. Dado que se pasa como un
tipo Object, la informacin de estado tiene que convertirse a su tipo apropiado antes de poderse utilizar.
La devolucin de llamada se realiza en un subproceso ThreadPool. Los subprocesos ThreadPool son
subprocesos en segundo plano, que no mantienen la aplicacin en ejecucin si el subproceso principal
finaliza, por lo que el subproceso principal del ejemplo debe permanecer en suspensin el tiempo suficiente
para que la devolucin de llamada finalice.
using System;
using System.Threading;
using System.Runtime.Remoting.Messaging;

MCT: Luis Dueas Pag 77 de 80


Modelos para la Programacin Asincrnica en .NET

namespace Examples.AdvancedProgramming.AsynchronousOperations
{
public class AsyncMain
{
static void Main()
{
// Create an instance of the test class.
AsyncDemo ad = new AsyncDemo();
// Create the delegate.
AsyncMethodCaller caller = new AsyncMethodCaller(ad.TestMethod);
// The threadId parameter of TestMethod is an out parameter, so
// its input value is never used by TestMethod. Therefore, a dummy
// variable can be passed to the BeginInvoke call. If the threadId
// parameter were a ref parameter, it would have to be a class-
// level field so that it could be passed to both BeginInvoke and
// EndInvoke.
int dummy = 0;
// Initiate the asynchronous call, passing three seconds (3000 ms)
// for the callDuration parameter of TestMethod; a dummy variable
// for the out parameter (threadId); the callback delegate; and
// state information that can be retrieved by the callback method.
// In this case, the state information is a string that can be
// used to format a console message.
IAsyncResult result = caller.BeginInvoke(3000,
out dummy, new AsyncCallback(CallbackMethod),
"The call executed on thread {0}, with return value
\"{1}\".");
Console.WriteLine("The main thread {0} continues to execute...",
Thread.CurrentThread.ManagedThreadId);
// The callback is made on a ThreadPool thread. ThreadPool threads
// are background threads, which do not keep the application
// running if the main thread ends. Comment out the next line to
// demonstrate this.
Thread.Sleep(4000);
Console.WriteLine("The main thread ends.");
}

// The callback method must have the same signature as the


// AsyncCallback delegate.
static void CallbackMethod(IAsyncResult ar)
{
// Retrieve the delegate.
AsyncResult result = (AsyncResult) ar;
AsyncMethodCaller caller = (AsyncMethodCaller)
result.AsyncDelegate;
// Retrieve the format string that was passed as state information
string formatString = (string) ar.AsyncState;
// Define a variable to receive the value of the out parameter.
// If the parameter were ref rather than out then it would have to
// be a classlevel field so it could also be passed to BeginInvoke
int threadId = 0;
// Call EndInvoke to retrieve the results.
string returnValue = caller.EndInvoke(out threadId, ar);
// Use the format string to format the output message.
Console.WriteLine(formatString, threadId, returnValue);
}
}
}

/* This example produces output similar to the following:


The main thread 1 continues to execute... Test method begins.
The call executed on thread 3, with return value "My call time was 3000.".
The main thread ends.*/

3.2.2. Ejemplo de programacin de delegados asincrnicos

MCT: Luis Dueas Pag 78 de 80


Modelos para la Programacin Asincrnica en .NET

El siguiente ejemplo de cdigo muestra cmo utilizar la programacin asincrnica de .NET mediante una clase que
factoriza algunos nmeros. Este ejemplo define una clase con un mtodo Factorize nico que calcula los factores
principales de un nmero especificado. El ejemplo tambin define un delegado denominado AsyncFactorCallercon
una firma que coincide con la firma del mtodo Factorize. Los mtodos utilizan el delegado en la
clase DemonstrateAsyncPattern para llamar de forma asincrnica al mtodo Factorize. El mtodo Factorize
NumberUsingCallback muestra cmo utilizar un delegado AsyncCallback para finalizar la operacin asincrnica y
obtener los resultados. El mtodo FactorizeNumberAndWait muestra el sondeo peridico para determinar si la
operacin ha finalizado.

Ejemplo
using System;
using System.Threading;
using System.Runtime.Remoting.Messaging;

namespace Examples.AdvancedProgramming.AsynchronousOperations
{
// Create a class that factors a number.
public class PrimeFactorFinder
{
public static bool Factorize(int number, ref int primefactor1,
ref int primefactor2)
{
primefactor1 = 1;
primefactor2 = number;
// Factorize using a low-tech approach.
for (int i=2;i<number;i++)
{
if (0 == (number % i))
{
primefactor1 = i;
primefactor2 = number / i;
break;
}
}
if (1 == primefactor1 )
return false;
else
return true;
}
}

// Create an asynchronous delegate that matches the Factorize method.


public delegate bool AsyncFactorCaller (int number,
ref int primefactor1, ref int primefactor2);

public class DemonstrateAsyncPattern


{
// The waiter object used to keep the main application thread
// from terminating before the callback method completes.
ManualResetEvent waiter;
// Define the method that receives a callback when the results are
// available.
public void FactorizedResults(IAsyncResult result)
{
int factor1=0;
int factor2=0;
// Extract the delegate from the
// System.Runtime.Remoting.Messaging.AsyncResult.
AsyncFactorCaller factorDelegate =
(AsyncFactorCaller)((AsyncResult)result).AsyncDelegate;
int number = (int) result.AsyncState;
// Obtain the result.
bool answer = factorDelegate.EndInvoke(ref factor1, ref factor2,
result);
// Output the results.
Console.WriteLine("On CallBack: Factors of {0} : {1} {2} - {3}",

MCT: Luis Dueas Pag 79 de 80


Modelos para la Programacin Asincrnica en .NET

number, factor1, factor2, answer);


waiter.Set();
}

// The following method demonstrates the asynchronous pattern using a


// callback method.
public void FactorizeNumberUsingCallback()
{
AsyncFactorCaller factorDelegate = new AsyncFactorCaller
(PrimeFactorFinder.Factorize);
int number = 1000589023;
int temp=0;
// Waiter will keep the main application thread from ending before
// the callback completes because the main thread blocks until the
// waiter is signaled in the callback.
waiter = new ManualResetEvent(false);
// Define the AsyncCallback delegate.
AsyncCallback callBack = new AsyncCallback(this.FactorizedResults);
// Asynchronously invoke the Factorize method.
IAsyncResult result = factorDelegate.BeginInvoke(number, ref temp,
ref temp, callBack, number);
// Do some other useful work while waiting for the asynchronous
// operation to complete. When no more work can be done, wait.
waiter.WaitOne();
}

// The following method demonstrates the asynchronous pattern


// using a BeginInvoke, followed by waiting with a time-out.
public void FactorizeNumberAndWait()
{
AsyncFactorCaller factorDelegate = new AsyncFactorCaller
(PrimeFactorFinder.Factorize);
int number = 1000589023;
int temp=0;
// Asynchronously invoke the Factorize method.
IAsyncResult result = factorDelegate.BeginInvoke(number,
ref temp, ref temp, null, null);
while (!result.IsCompleted)
{
// Do any work you can do before waiting.
result.AsyncWaitHandle.WaitOne(10000, false);
}
result.AsyncWaitHandle.Close();
// The asynchronous operation has completed.
int factor1=0;
int factor2=0;
// Obtain the result.
bool answer = factorDelegate.EndInvoke(ref factor1, ref factor2,
result);
// Output the results.
Console.WriteLine("Sequential : Factors of {0} : {1} {2} - {3}",
number, factor1, factor2, answer);
}

public static void Main()


{
DemonstrateAsyncPattern demonstrator = new
DemonstrateAsyncPattern();
demonstrator.FactorizeNumberUsingCallback();
demonstrator.FactorizeNumberAndWait();
}
}
}

MCT: Luis Dueas Pag 80 de 80

Potrebbero piacerti anche