Esta es la segunda entrega de la serie que dedico a los procesos asíncronos.
La primera entrega la puedes consultar en
este enlace.
Como dijimos anteriormente, con el Framework 4.5 se incluyen algunas instrucciones nuevas en la TPL (Task Parallel Library) como son async, await, y otras contenidas en Task (Delay, FromResult, Run, WhenAll, WhenAny)
Si bien los comandos Async Await se pueden conseguir hacerlos funcionar a partir de VS2012 con el Framework 4.0 importando desde NuGet las librerías
Microsoft.Bcl.Async (subconjunto de las TPL del 4.5), el resto de comandos solo están disponibles con el framework 4.5
Un poco de código
Supongamos que volvemos a tener ese proceso largo que nos paraliza la ejecución del hilo.
Lo primero que deberíamos de hacer es encapsularlo dentro de una función que devuelva un Task<tipo>, donde tipo es la devolución de la función.
Si la función no devuelve nada, podremos devolver un Task.
Se recomienda que la función tenga un nombre acabado en "async" por claridad, y que devuelva un Task. Se podría devolver un void, pero no es lo recomendable (
Aquí y
aquí comentan las razones por las que no recomiendan void functions)
private static Task<string> ProcesoLargoAsync (string texto)
{
Task<string> t = Task.Run(() => {
int tiempo = new Random().Next(1000, 6000);
Thread.Sleep(tiempo);
string texto2 = texto.ToUpper();
return texto2;
});
return t;
}
Task.Run (que es nuevo en el 4.5) cumple el mismo cometido que Task.Factory.StartNew: Declarar y arrancar el proceso.
A continuación, llamaremos a esta función que devuelve una tarea con otra que es la que la marcaremos como asíncrona.
public static async void PruebaDeAsync0()
{
string texto = await ProcesoLargoAsync("Hello world!!!");
Console.WriteLine(texto);
}
Y la llamaremos normalmente.
Console.WriteLine("Empezamos hilo principal con Async-Await");
PruebaDeAsync0();
Console.WriteLine("Hilo principal acabado");
En el momento que el hilo de ejecución pase por la función marcada como async, la ejecutará un segundo hilo, parándose en el await hasta que la función ProcesoLargoAwait responda, y, a continuación, imprimirá el resultado por consola.
Mientras, el hilo principal no se ha detenido y ha impreso el "Hilo principal acabado".
Listas
Mediante las funciones Task.whenAny o Task.WhenAll podremos controlar la ejecución de las tareas
public static async void PruebaDeAsync1()
{
List<Task<string>> lista = new List<Task<string>>()
ProcesoLargoAsync("Hello world 1 !!!"),
ProcesoLargoAsync("Hello world 2 !!!"),
ProcesoLargoAsync("Hello world 3 !!!"),
ProcesoLargoAsync("Hello world 4 !!!"),
ProcesoLargoAsync("Hello world 5 !!!")
};
var y = await Task.WhenAny(lista);
Console.WriteLine("WhenAny: Se ha acabado la tarea {0}", y.Result);
var x = await Task.WhenAll(lista);
Console.WriteLine("Todas las tareas acabadas");
}
La llamada a la función:
Console.WriteLine("Empezamos hilo principal de listas Async-Await");
PruebaDeAsync1();
Console.WriteLine("Hilo principal acabado");
En este caso la instrucción await Task.WhenAny(lista) bloqueará el hilo asíncrono hasta que alguna tarea acabe.
La instrucción await Task.WhenAll(lista) volverá a bloquear el hilo asíncrono hasta que todas las tareas acaben.
También se podría haber llamado a cada tarea y haberle aplicado un ContinueWith para que cuando acabe cada tarea notifique que ha acabado.
List<Task<string>> lista = new List<Task<string>>()
{
ProcesoLargoAsync("Hello world 1 !!!"),
ProcesoLargoAsync("Hello world 2 !!!"),
ProcesoLargoAsync("Hello world 3 !!!"),
ProcesoLargoAsync("Hello world 4 !!!"),
ProcesoLargoAsync("Hello world 5 !!!"),
ProcesoLargoAsync("Hello world 6 !!!"),
ProcesoLargoAsync("Hello world 7 !!!"),
ProcesoLargoAsync("Hello world 8 !!!"),
ProcesoLargoAsync("Hello world 9 !!!"),
ProcesoLargoAsync("Hello world 10 !!!")
};
lista.ForEach(tarea=>tarea.ContinueWith((tareaEjecutada)=>
Console.WriteLine("Ha acabado la tarea "+tareaEjecutada.Result)));
await Task.WhenAll(lista);
Console.WriteLine("Todas las tareas acabadas");
Interfaces de usuario
En esta parte es donde más se aprecia el cambio.
Vimos en las TPL 4.0 que teníamos que andar con Invoke para poder ejecutar un código asíncrono en una interfaz ya que un proceso no tenía visibilidad sobre otro proceso.
Con la TPL 4.5 podemos hacer algo tan sencillo como:
private void button2_Click(object sender, EventArgs e)
{
TareaAsync();
}
public async void TareaAsync()
{
label1.Text = await ProcesoLargoAsync("Hello, world!!!");
}
Saludos.