csharp-async-await

Async y await en C#

En C#, async y await son palabras claves que simplifican la sintaxis al trabajar con tareas asíncronas, facilitando además su lectura.

  • async: Se coloca antes de un método para indicar que contiene operaciones asíncronas.
  • await: Pausa la ejecución hasta que se complete la tarea asíncrona y permite liberar el hilo para que ejecute otras tareas mientras tanto.

¿Cómo funcionan async y await?

Cuando un método se marca como async, significa que este método no bloquea al hilo que lo ejecuta (es decir, se ejecuta asíncronamente).

Por otro lado, un método async puede contener una o más operaciones marcadas con await. Esto significa que el método va a “esperar” que esta operación termine, antes de continuar.

Una vez que la operación “awaiteada” se completa, el método async retoma la ejecución en el punto donde quedó.

Es importante recordar que:

  • Los métodos async deben devolver Task, Task<T> o void.
  • await solo se puede utilizar dentro de métodos async.

Es un error típico pensar que hay que awaitear todos los métodos async. Es perfectamente válido querer lanzar un método async, y que este se ejecute en paralelo.

Ejemplo de async y await

Vamos a verlo con un ejemplo. Supongamos que tenemos una OperacionLarguisima que queremos ejecutar de forma no bloqueante.

En primer lugar, vamos a marcar el método como asincrónico utilizando la palabra clave async.

// Método asíncrono que simula una operación de larga duración
static async Task OperacionLarguisima()
{
	Console.WriteLine("Inicio de operación de larga duración...");
	
	await Task.Delay(3000);  // Simulamos un proceso de 3
	// Resto del código
	
	Console.WriteLine("Operación completada.");
}

Eso significa que cuando invoquemos esa función, se va a ejecutar de forma no bloqueante (en paralelo).

Ahora supongamos que queremos llamar a esa función. Podríamos hacerlo así.

static async Task Main()
{
	Console.WriteLine("Inicio del Main...");
	
	// Llama a un método asíncrono
	await OperacionLarguisima();

	Console.WriteLine("Fin del main.");
}

Si ejecutamos esto, tendremos de resultado

Inicio del Main...
Inicio de operación de larga duración...

# (una pausa de 3s).

Operación completada.
Fin del main.

Es decir,

  • La función Main ha empezado, y ha llamado a OperacionLarguisima
  • Main ha esperado que acabe OperacionLarguisima
  • Main ha finalizado

La “cadena” de asyncs

Una cosa curiosa del async y await es que “hacen cadeneta”. El método que marcas como async, tiene que ser invocado por un async, que tiene que ser invocado por…

Es decir, una vez que empiezas a usar métodos async, todo lo que lo use va a tener que ser async (hasta llegar al método Main principal). En algún momento vas a tener que “partir la cadeneta”.

Para ello, es recordad que podéis usar Task.Wait();. Por ejemplo, esta versión del Main anterior no es asíncrona.

static void MainSincrono()
{
	Console.WriteLine("Inicio del Main...");
	
	OperacionLarguisima.Wait();

	Console.WriteLine("Fin del main.");
}

Combinación de múltiples tareas asíncronas

En algunos casos, es necesario ejecutar múltiples tareas de forma asíncrona. Esto puede hacerse usando Task.WhenAll o Task.WhenAny.

  • Task.WhenAll: Ejecuta múltiples tareas de manera concurrente y espera hasta que todas se completen.
  • Task.WhenAny: Ejecuta múltiples tareas y retorna cuando cualquiera de ellas se complete.
static async Task EjecutarTareasConcurrently()
{
    Task tarea1 = Task.Delay(2000);  // Simula una tarea de 2 segundos
    Task tarea2 = Task.Delay(3000);  // Simula una tarea de 3 segundos

    // Espera hasta que ambas tareas se completen
    await Task.WhenAll(tarea1, tarea2);

    Console.WriteLine("Todas las tareas han finalizado.");
}