.NET Framework ofrece tres patrones para realizar operaciones asincrónicas:

  • El modelo de programación asincrónica (APM) (también denominado patrón IAsyncResult), donde las operaciones asincrónicas requieren los métodos Begin y End (por ejemplo, BeginWrite y EndWrite para las operaciones de escritura asincrónicas).No se recomienda este patrón en desarrollos nuevos.
    Para obtener más información, vea Modelo de programación asincrónica (APM).

  • El patrón asincrónico basado en eventos (EAP), que requiere un método que tiene el sufijo Async y uno o más eventos, tipos delegado de controlador de eventos y tipos derivados de EventArg.
    EAP apareció por primera vez en .NET Framework 2.0.No es recomendable en desarrollos nuevos. Para obtener más información, vea Patrón asincrónico basado en eventos (EAP).

  • El Modelo asincrónico basado en tareas (TAP), que usa un solo método para representar el inicio y la finalización de una operación asincrónica. TAP apareció por primera vez en .NET Framework 4 y es el enfoque recomendado para la programación asincrónica de .NET Framework. Para obtener más información, vea Modelo asincrónico basado en tareas (TAP).

Modelo asíncrono basado en Tareas

El patrón conocido como TAP (por sus siglas en inglés: Task-based Asynchronous Pattern) es el nuevo patrón asincrónico basado en el espacio de nombres System.Threading.Tasks (en particular las clases Task y Task<TResult>) y como mencionamos en la introducción, es el enfoque recomendado para la programación asincrónica en .NET Framework 4 y en versiones posteriores.
Visual Studio 2010 (.NET 4.0) trajo como novedad el espacio de nombres System.Threading.Tasks dentro de lo que se llamó TPL (Task Parallel Library) o en español biblioteca de procesamiento paralelo basado en tareas.

Programación asincrónica con Async y Await

Pero fue recién en Visual Studio 2012 (en las versiones 5.0 y 11.0 de los lenguajes C# y VB, respectivamente) que a través del agregado de nuevas palabras claves a esos lenguajes (await/Await y async/Async), se simplificó la manera de realizar programación asincrónica en .NET. Puede leer más acerca de este tema haciendo click aquí.

async

Esta palabra clave se aplica a la declaración de un método pero, contra lo que suele pensar un principiante, no declara que un método se ejecuta asíncronamente. La palabra clave async indica que este método es sincronizable con métodos que se ejecutarán de forma asíncrona.
De hecho, si no usamos async podremos seguir llamando métodos de forma asíncrona (lo que ya hacíamos anteriromente), lo que no podremos hacer (de forma trivial) es sincronizarnos con este método asíncrono, o sea esperar a que termine. Ni más, ni menos.

Es como si fuéramos a una pizzería con un amigo y nos sentamos a la mesa, comenzamos a hablar de fútbol y cuando se acerca el empleado hacemos el pedido de una pizza. Mientras esperamos que nos la traigan, seguimos hablando de fútbol, y nos quedamos esperando la pizza hasta que el empleado nos la traiga. Cuando el camarero llega, comemos la pizza y seguimos hablando.

Nota: Declarar un método como async es requisito indispensable para poder usar await.

 

await

Esa palabre clave es la que permite que un método que ha llamado a otro método asíncrono espere a que dicho método asíncrono termine. Usando de nuevo el ejemplo de la pizzería, cuando queremos comer pizza, debemos esperar a que llegue el empleado con la pizza.
La clave de todo es entender que desde el momento en que nos sentamos (llamada al método asíncrono) hasta el momento en que pedimos la pizza y comenzamos a esperarla, hemos estado haciendo otras cosas (hablando de fútbol). Eso, de nuevo trasladado a código fuente, significa que entre la llamada al método asíncrono y el uso de await habrá más líneas de código fuente. Por lo tanto no usamos await cuando llamamos al método asíncrono, lo hacemos más tarde cuando queremos esperar a que dicho método termine (y recoger el resultado, es decir la pizza).

Entonces, ¿sobre que aplicamos await? Pues todo método que quiera ser ejecutado asíncronamente debe devolver un objeto especial, que sea esperable (awaitable). Sobre este objeto, es sobre el que llamaremos a await para esperar a que el método asíncrono finalice y a la vez obtener el resultado. ¿Y que es un objeto awaitable? Pues un conocido de la TPL (Task Paralell Library) que, como dijimos anteriormente ya venía en Visual Studio 2010 y .NET 4: Un objeto Task o su equivalente genérico Task.

Métodos asíncronos

Para declarar un método que pueda ser llamado de forma asíncrona, lo único que debemos hacer es devolver un Task o Task<T> desde este método.
Pero, devolver un Task NO convierte el método en asíncrono. Es la propia Task que es asíncrona. Podemos ver una Task como un delegate (o sea un método) que puede ser ejecutado de forma asíncrona.
Trasladando eso de nuevo al ejemplo de la pizzería, cuando pedimos la pizza, le hemos encargado esta tarea al empleado y la hemos puesto en marcha. En términos de C# cuando llamo al método ServirPizza de la clase Pizzeria, este método me devuelve una Task, que representa la tarea que he encargado al empleado. Luego tenemos que poner en marcha esa tarea (con lo cual el empleado iniciará el proceso de la fabricacion de la pizza y finalmente nos la traerá) y cuando toque esperar llamaremos a await sobre el objeto Task. El resultado de llamar a await sobre una Task es precisamente un objeto de la clase Pizza.
Vamos a ver el ejemplo de la pizza implementado en C# para que nos queden los conceptos más claros:

  1. Iniciamos Visual Studio 2013 y creamos una nueva aplicación de consola, la llamamos PedidoPizza.

Nueva Console App

2 . Agregamos la clase Pizza con el siguiente código:

namespace PedidoPizza
{
     public class Pizza
     {
        public string Tipo { get; set; }
        public Pizza(string t)
        {
            this.Tipo = t;
        }
     }
}

3 . Ahora agregamos la clase Pizzeria con el siguiente código:

    using System.Threading.Tasks;
    namespace PedidoPizza
    {
        class Pizzeria
        {
            public Task<Pizza>ServirPizza(string tipo)
            {
                return new Task<Pizza>(() =>
                    {
                        return new Pizza(tipo);
                  });
            }
        }
    }

4 . Luego, escribimos el siguiente método Main() en la clase Program.cs:

    static void Main(string[] args)
    {
        Console.WriteLine("Pidiendo una pizza de pepperoni con mi amigo");
        Console.WriteLine("============================================");
        Console.WriteLine();

        Task t = PrimeraEspera();
        t.Wait();

        Task t2 = EsperandoLaPizza();
        t2.Wait();
        Console.WriteLine("BUEN PROVECHO!");
        Console.WriteLine();
        HablandoDeFutbol();
        Console.WriteLine("Pulse cualquier tecla para finalizar...");
        Console.ReadKey(true); 
    }

5 . Finalmente agregamos en la clase Program, los tres métodos que hacen falta:

public async static Task PrimeraEspera()
{
    var miPizza = new Pizza("Pepperoni");
    Console.WriteLine("Llegamos a la pizzería a las {0} y esperamos para hacer el pedido...", 
            DateTime.Now.ToString("T"));
    HablandoDeFutbol();
    await Task.Delay(10000);
    Console.WriteLine("Pedimos una pizza de {0} a las {1}", 
            miPizza.Tipo, DateTime.Now.ToString("T"));
}

private static async Task<Pizza> EsperandoLaPizza()
{
     Pizzeria dominos = new Pizzeria();
     var task = dominos.ServirPizza("Pepperoni");
     task.Start();
     HablandoDeFutbol();
     //Llega el empleado y hacemos el pedido y nos quedamos esperando...
     var miPizza = await task;
     await Task.Delay(10000);
     Console.WriteLine("Nos traen la pizza de {0} a las {1}", 
              miPizza.Tipo, DateTime.Now.ToString("T"));
     return miPizza;
}

private static void HablandoDeFutbol()
{
    Console.WriteLine("...hablando de futbol...");
    Console.WriteLine("Bla bla bla....");
    Console.WriteLine();
}

6 . Corremos el programa, con depuración pulsando F5, y obtenemos la siguiente salida:

Salida

Acerca del Autor

respag

respag

Fanático de la programación y de San Lorenzo de Almagro. Argentino de nacimiento y panameño por adopción. Profesor de Matemáticas, autodidacta y docente de alma.