Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Programacin
Asincrnica .NET
Modelos para la Programacin Asincrnica en .NET
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);
}
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.
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
(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.
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);
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.
Sin embargo, muchas implementaciones TAP no proporcionan ni la cancelacin ni las capacidades de progreso, por lo
que requieren un nico mtodo:
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.
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);
}
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.
{
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.
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;
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; }
}
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:
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();
}
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)
{
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)
{
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
{
Task<Bitmap> imageTask = await Task.WhenAny(imageTasks);
imageTasks.Remove(imageTask);
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;
{
if (m_cts != null) m_cts.Cancel();
}
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.
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; }
}
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; }
}
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:
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;
}
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;
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:
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.
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>(
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.
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.
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.
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.
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.
Visual Basic
Admisin de varias operaciones
Slo una operacin cada vez
simultneas
C#
Admisin de varias operaciones
Slo una operacin cada vez
simultneas
Informacin de progreso
Implementacin de IsBusy
Cancelacin
Errores y excepciones
Subprocesamiento y contextos
Instrucciones
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.
// 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.
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.
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.
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.
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.
Crear el componente
El primer paso es crear el componente que implementar el modelo asincrnico basado en eventos.
Punto de control
En este punto, ya puede compilar el componente.
lock (userStateToLifetime.SyncRoot)
{
userStateToLifetime.Remove(asyncOp.UserSuppliedState);
}
}
Punto de control
En este punto, ya puede compilar el componente.
Nota
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;
}
get
{
return latestPrimeNumberValue;
}
}
}
Punto de control
En este punto, ya puede compilar el componente.
{
lock (userStateToLifetime.SyncRoot)
{
userStateToLifetime.Remove(taskId);
}
}
}
Punto de control
En este punto, ya puede compilar el componente.
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.
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
/////////////////////////////////////////////////////////////
#region Public events
#endregion
/////////////////////////////////////////////////////////////
#region Construction and destruction
public PrimeNumberCalculator()
{
InitializeComponent();
InitializeDelegates();
}
/////////////////////////////////////////////////////////////
#region Implementation
userStateToLifetime[taskId] = asyncOp;
}
/////////////////////////////////////////////////////////////
#region Component Designer generated code
#endregion
}
}
}
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
/////////////////////////////////////////////////////////////
// Construction and destruction
#region Private fields
public PrimeNumberCalculatorMain ()
{
// Required for Windows Form Designer support
InitializeComponent();
this.primeNumberCalculator1.ProgressChanged +=
new ProgressChangedEventHandler(
primeNumberCalculator1_ProgressChanged);
this.listView1.SelectedIndexChanged +=
new EventHandler(listView1_SelectedIndexChanged);
}
/////////////////////////////////////////////////////////////
#region Implementation
{
this.cancelButton.Enabled = CanCancel();
}
{
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
}
return lviRet;
}
#endregion
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";
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
/////////////////////////////////////////////////////////////
#region Public events
#endregion
/////////////////////////////////////////////////////////////
#region Construction and destruction
public PrimeNumberCalculator()
{
InitializeComponent();
InitializeDelegates();
}
/////////////////////////////////////////////////////////////
#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);
}
{
return( userStateToLifetime[taskId] == null );
}
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;
}
#endregion
/////////////////////////////////////////////////////////////
#region Component Designer generated code
#endregion
}
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);
}
Miembro Descripcin
Nota
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.
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;
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);
}
}
}
}
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);
}
}
}
}
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");
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()
{
// 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());
}
}
}
}
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;
return hostName;
}
}
}
// 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());
}
}
}
}
}
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.
Nota
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.
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);
}
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.
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);
}
}
}
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);
}
}
}
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.");
}
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;
}
}