
En este artículo encontraréis una breve introducción a los diferentes modelos de ejecución asíncrona que tenemos en .Net y cómo podemos utilizar algunos de los nuevos que no vienen de serie en Windows Phone 7, como ocurre con TPL.
Poco a poco los usuarios se volvieron cada vez más impacientes exigentes y dejaron de admitir aquellas agradables pausas que les ofrecíamos los programadores para que pudieran relajarse y relacionarse con sus compañeros. Los fabricantes de sistemas operativos crearon la multitarea: podíamos utilizar varias aplicaciones simultáneamente, así que los usuarios vieron la luz y se pusieron muy pesados con el resto de desarrolladores, los pobres mortales que escribían aplicaciones de gestión, exigiendo que las aplicaciones no se quedaran paradas mientras realizaban alguna complicada operación que les parecía que tardaba demasiado.
Por mucho que optimicemos existen multitud de esperas que no dependen de nosotros. A algunos se les ocurrió que valía más parecer rápido que serlo y vieron que la solución era utilizar el tiempo que el procesador estaba ocioso para realizar las tareas largas, procurando no influir demasiado sobre el rendimiento del interfaz de usuario.

La programación con múltiples hilos puede llegar a ser muy complicada y difícil de entender, pues al estar acostumbrados a un lenguaje imperativo para programar (como en nuestro caso C#) tendemos a pensar en la ejecución de manera secuencial. Cuando utilizamos múltiples hilos rompemos esa secuencia en tareas que pueden ejecutarse en paralelo.
En .Net podemos hacer uso de la clase Thread que encapsula la funcionalidad de los hilos del sistema operativo, pero para facilitarnos las tareas más comunes surgieron otros dos patrones de desarrollo de métodos asíncronos. Estos dos patrones nos facilitaron mucho la tediosa tarea de ir creando, manteniendo y sincronizando diferentes hilos de ejecución:
Utilizando un manejador de evento explícito podemos tratar el resultado del método asíncrono cuando este acaba su ejecución de la siguiente manera:
public void DescargarDatos()
{
var webClient = new WebClient();
webClient.DownloadStringCompleted += webClientDownloadStringCompleted;
webClient.DownloadStringAsync(new Uri("http://jmservera.wordpress.com/feed/"));
this.contenedor.Text="DescargaComenzada";
}
private void webClientDownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
this.contenedor.Text=e.Result;
}
Cuando usamos este método en Silverlight el sistema volverá a sincronizar el hilo de UI en el evento Completed para que no tengamos que utilizar explícitamente el Dispatcher. Puede que alguna implementación de llamada asíncrona EAP no tenga en cuenta esta característica de la programación en el UI.
Con este modelo tenemos el manejador del resultado algo lejos de la llamada y en códigos muy largos podemos perder un poco la perspectiva de lo que estamos haciendo.
Gracias a los métodos anónimos podemos escribir directamente dentro del mismo método, lo que nos facilitará un poco la legibilidad de nuestro código:
public void DescargarDatos()
{
var webClient = new WebClient();
webClient.DownloadStringCompleted += (o,e) =>
{
this.contenedor.Text=e.Result;
};
webClient.DownloadStringAsync(new Uri("http://jmservera.wordpress.com/feed/"));
this.contenedor.Text="DescargaComenzada";
}
Aun así la secuencia natural no se ve reflejada en el código, pues la acción que pretendemos realizar tras la descarga la tenemos que indicar antes de llamar a la ejecución de la misma. Nos queda el código desordenado.
public void DescargarDatosTPL()
{
var syncContext= TaskScheduler.FromCurrentSynchronizationContext();
var webClient = new WebClient();
//ejecutamos la tarea asincrona
webClient.DownloadStringTaskAsync(new Uri("http://jmservera.wordpress.com/feed/"))
.ContinueWith((s) =>
{
//y continuamos tras finalizar la descarga
contenido.Text = s.Result;
}, syncContext);
// aquí continuamos en el hilo principal mientras se ejecuta la tarea
// de descarga
contenido.Text = "Descargando...";
}
Como podemos ver en el código las llamadas se encadenan en tareas en la secuencia natural, primero descargamos y luego utilizamos el resultado.
Para poder asignar el texto al contenedor hemos utilizado un TaskScheduler que sincronizará automáticamente el hilo de ejecución con el contexto que nosotros le indicamos, no nos tenemos que preocupar de llamar al Dispatcher, si hace falta el TaskScheduler se encargará de ello por nosotros.
public static class WebClientExtension
{
public static Task<string> DownloadStringTaskAsync(this WebClient webClient, Uri uri)
{
TaskCompletionSource<string> tcs =
new TaskCompletionSource<string>();
DownloadStringCompletedEventHandler completedHandler = null;
completedHandler=(o, e) =>
{
if (e.Cancelled)
{
tcs.TrySetCanceled();
}
else if (e.Error != null)
{
tcs.TrySetException(e.Error);
}
else
{
tcs.TrySetResult(e.Result);
}
//hay que limpiar referencias para
//evitar problemas de memoria
webClient.DownloadStringCompleted -= completedHandler;
};
webClient.DownloadStringCompleted += completedHandler;
try
{
webClient.DownloadStringAsync(uri);
}
catch (Exception ex)
{
tcs.TrySetException(ex);
webClient.DownloadStringCompleted -= completedHandler;
}
return tcs.Task;
}
}
Una vez creada la tarea de esta manera podremos llamar al método DownloadStringTaskAsync como hicimos en el ejemplo anterior.
Como hemos visto poco a poco vamos mejorando la legibilidad del código, de manera que nuestro código se parezca cada vez más al código que escribiríamos si realizáramos la operación de manera síncrona. La última vuelta de tuerca ha sido la creación de las nuevas construcciones del lenguaje async y await.
Durante el Mix 2011 se anunció la Community Technology Preview de Async, una nueva característica del lenguaje C# para la creación de funciones asíncronas, que está madurando y, como se vio en el Build Windows, ya forma parte del framework 4.5. La nueva manera de escribir nuestro código será ésta:
async void DescargarDatosAsync()
{
var webClient = new WebClient();
var data = await webClient.DownloadStringTaskAsync(
new Uri("http://jmservera.wordpress.com/feed/"));
this.contenedor.Text = data;
}
El truco está en la palabra reservada await que le indica al compilador que llamamos a un método asíncrono y que debe continuar la ejecución hasta que encuentre código que solicita los datos esperados del método asíncrono. Para ello el compilador creará una máquina de estados que generará los métodos necesarios para manejar los callbacks que sean necesarios.
Para Windows Phone 7.1 la última versión de la Async CTP no funciona, así que para probarla necesitaréis el Visual Studio 11 Developer Preview.
Cuando llamemos al método DescargarDatosAsync nuestro código no se parará ahí, continuará la ejecución y quien se encarga de todo esto es el propio compilador de C#.
¿Sabes cuál fue el primer sistema operativo doméstico con multitarea preemtiva? Descúbrelo en los enlaces de este artículo.