miércoles, 27 de marzo de 2013

C#: Introducción a Task async based Pattern en Visual Studio 2012

Esta semana ya he instalado Visual Studio 2012 (algunos pensarán ya era hora jajajja). La intención es mirarme un poco el desarrollo de la nueva línea de diseño “metro” que me llama bastante. Ya os iré contando, inevitablemente algún artículo caerá jeje.

Una de las novedades interesantes que incorpora Visual Studio 2012 respecto a C# y vb es la gestión de hilos asíncronos basado en TAP pattern Task based Async pattern. En este otro artículo ya exponíamos en areaTIC las ventajas de la programación asíncrona respecto programación síncrona y por otro lado vimos que una de las desventajas es que la gestión del código se complica a medida que vamos implementando asíncronos. Con este nuevo patrón se pretende simplificar la gestión de código asíncrono respecto otros patrones como IAsyncPattern.

A partir del framework 4.5 algunas clases del Framework ya incorporan métodos que implementan el patrón async y pueden usarse, como por ejemplo, System.Threading.Stream.CopyToAsync. El siguiente link de MSDN explica perfectamente cómo usar estos métodos usando el patrón TAP. En este ejemplo estamos usando un método del framework que ya está preparado para ser invocado vía TAP, a continuación veremos cómo convertir un método cualquiera para poder ser invocado vía TAP.

En principio si estamos trabajando con framework 4.5 y Visual Studio 2012 deberíamos poder añadir el modificador async a la firma de cualquiera de nuestros métodos, podemos hacer la prueba y tal vez recibiremos alguna advertencia pero compilará y podremos seguir usando el método una vez marcado con el modificador async. Hasta aquí simplemente hemos marcado un método como async pero no hemos hecho suficiente para garantizar que nuestro código sea asíncrono. Hacer que el código nuestro código sea asíncrono siguiendo este patrón no implica seguir siempre los mismos pasos si no que dependerá un poco del comportamiento que queramos aplicar en cada caso.

Es importante entender el objeto Task, ya que se encarga de controlar el estado de la llamada asíncrona y proporcionar información sobre esta al hilo “padre”. Resumiendo y simplificando mucho, podemos invocar un método (que implementa TAP) y guardarnos un objeto Task, realizar ciertas acciones y posteriormente antes de salir del método “padre” recuperar el valor de respuesta de la primera llamada usando el comando await Task. (en este artículo no profundizamos pero a partir de Task se puede tener acceso al progreso de la tarea, estado, excepciones, …)

Para aplicar el patrón en un escenario diferente al típico de descargar contenido de una URL o escribir un fichero en disco, partimos de un ejemplo anterior de un artículo de areaTIC donde se explicaba como implementar IAsyncPattern. En el ejemplo simulamos tener 3 procesos que realizan trabajos de computación pesados y vimos como hacer 3 llamadas en 3 threads diferentes para evitar tener que esperar a que finalice un proceso para que el hilo de ejecución procese el siguiente… Con TAP se puede llegar a hacer lo mismo, veamos un par de alternativas sobre el mismo caso de uso.

En primer lugar tenemos que hacer que los métodos Proceso1, Proceso2, Proceso3 de la clase implementen el patrón TAP es decir que devuelvan un objeto Task en este caso. Veaamos como hacerlo:
class Proceso
{
        
   public Task ProcesoNegocio1(string pEntrada)
   {
     return Task.Run(() =>
     {
        //dormimos ejecución durante 7 segundos 
        //simulando una acción que tarda ese tiempo.
        System.Threading.Thread.Sleep(7000);
        //devolvemos fecha fin proceso concatenada a la fecha de entrada
        return string.Format("Fin Proceso 1: {0}-{1}", pEntrada, DateTime.Now.ToString());
            }, new CancellationToken());
    }

    public Task ProcesoNegocio2(string pEntrada)
    {
     return Task.Run(() =>
     {
       //dormimos ejecución durante 10 segundos 
       //simulando una acción que tarda ese tiempo.
       System.Threading.Thread.Sleep(10000);
       //devolvemos fecha fin proceso concatenada a la fecha de entrada
       return string.Format("Fin Proceso 2: {0}-{1}", pEntrada, DateTime.Now.ToString());
     }, new CancellationToken());
    }

    public Task ProcesoNegocio3(string pEntrada)
    {
     return Task.Run(() =>
     {
        //dormimos ejecución durante 3 segundos 
        //simulando una acción que tarda ese tiempo.
        System.Threading.Thread.Sleep(3000);
        //devolvemos fecha fin proceso concatenada a la fecha de entrada
        return string.Format("Fin Proceso 3: {0}-{1}", pEntrada,    DateTime.Now.ToString());
     }, new CancellationToken());
   }
}
Si os fijáis en estos métodos no hemos usado el modificador async en la firma, el modificador debería tenerlo el método “padre” que llamará a estos y realizará la gestión del multithreading. Veamos el método “padre” como quedaría:
async private void button1_Click(object sender, EventArgs e)
{
  Proceso proceso = new Proceso();
  Task result1 = proceso.ProcesoNegocio1(DateTime.Now.ToString());
  Task result2 = proceso.ProcesoNegocio2(DateTime.Now.ToString());
  Task result3 = proceso.ProcesoNegocio3(DateTime.Now.ToString());
  Console.WriteLine(await result1);
  Console.WriteLine(await result2);
  Console.WriteLine(await result3);
}
A priori con esto ya haríamos que el hilo de ejecución principal procese en un thread diferente cada una de las 3 llamadas, de modo que estos realizarán el proceso y quedarán a la espera que solicitemos el resultado mediante el comando await.

Tal como hemos dejado el código obtendremos las respuestas por orden y mostraríamos el resultado, si os fijáis en el tiempo de entrada y fin de cada proceso que escribimos en consola veréis que la gestión ha sido asíncrona y que no necesariamente el orden de finalización de cada método coincide con el orden en que solicitamos la respuesta usando await. Podríamos haber llegado a usar IEnumarable.WhenAll para esperar a que finalicen las 3 tareas por aquello de ser más elegantes pero es un ejemplo de batalla... En definitiva esto podría llegar a complicarse bastante más de lo que hago en este el artículo. En el siguiente link se explica a fondo como tratar excepciones, progreso, cancelar, etc… con ejemplos… es uno de los 'tutoriales' más claros que he encontrado en la web (descargar documento word).

Hasta aquí el artículo de hoy se agradecen vuestros comentarios, debates, valoraciones, etc… que sino esto es muy aburrido jajaja. Como siempre recordaros que podéis seguir areaTIC en las redes sociales.


No hay comentarios:

Publicar un comentario