csharp-tasks

Qué son y cómo usar Tasks en C#

Las Tasks son una forma de manejar de ejecutar tareas asíncrona en .NET. Permiten realizar operaciones en segundo plano sin bloquear el hilo principal, lo que resulta en una mejora significativa en la experiencia de usuario y en la eficiencia del programa.

Los Tasks son útiles, por ejemplo

  • Operaciones de Entrada/Salida (I/O): como leer o escribir en un archivo, acceso a bases de datos, o realizar peticiones HTTP.
  • Procesamiento intensivo: operaciones que consumen mucha CPU y que podrían ralentizar la interfaz de usuario en aplicaciones de escritorio o móviles.
  • Ejecución paralela: cuando se necesita ejecutar múltiples tareas al mismo tiempo y esperar sus resultados.

¿Qué son los Tasks en C#?

Una Task es un objeto que representa una tarea que se está ejecutando o que está pendiente de ejecución. Esta tarea puede estar compuesta de varias operaciones, y pueden ser ejecutadas en paralelo con otras Tasks.

En C#, un Task es una clase (System.Threading.Tasks.Task), y tiene un estado que indica su progreso y una vez que se completa, su resultado puede ser recuperado.

Creación y uso básico de Tasks

Un Task se puede crear de varias maneras en C#. Las formas más comunes son:

  • Usando la clase Task directamente
  • Utilizando async y await

Creación de un Task

Para crear un Task básico, puedes utilizar la clase Task e iniciar una nueva tarea con Task.Run() o Task.Factory.StartNew().

Por ejemplo, si se desea crear una Task para realizar una operación matemática, se puede escribir lo siguiente:

Task t = Task.Run(() => {
  int result = 1 + 2;
  
  Task.Delay(2000).Wait(); // Espera 2 segundos
  
  Console.WriteLine("Resultado: " + result);
});

Aquí,

  • Task.Run: Crea y ejecuta un Task que se ejecutará de forma asíncrona.
  • Task.Delay: Simula una tarea de espera (en este caso, 2 segundos) antes de terminar la ejecución del Task.

Esperar la finalización de una Task

Una vez que se ha creado una Task, puede ser necesario esperar a que se complete antes de continuar con otras operaciones.

Para hacer esto, se puede utilizar el método Wait() de la Task, que bloquea el hilo actual hasta que la Task se complete.

Task t = Task.Run(() => {
  int result = 1 + 2;
  Console.WriteLine("Resultado: " + result);
});
t.Wait();

Uso de async y await

Para simplificar el manejo de Tasks asíncronos, C# introduce las palabras clave async y await. El modificador async se coloca en la declaración de un método, y await permite esperar a que un Task termine sin bloquear el hilo principal. Aquí un ejemplo básico:

public static async Task Main()
{
	Console.WriteLine("Inicio de la operación asíncrona...");
	await OperacionAsincrona();
	Console.WriteLine("Operación completada.");
}

public static async Task OperacionAsincrona()
{
	await Task.Delay(2000); // Espera asíncrona de 2 segundos
	Console.WriteLine("Dentro de la operación asíncrona.");
}

Manejo de excepciones en Tasks

El manejo de excepciones puede llegar a ser algo complicado. Las excepciones en los Tasks no se propagan inmediatamente al hilo principal; en su lugar, quedan almacenadas en el Task y deben capturarse usando bloques try-catch junto con await o task.Wait().

using System;
using System.Threading.Tasks;

public class Program
{
    public static async Task Main()
    {
        try
        {
            await Task.Run(() => { throw new InvalidOperationException("Error en el Task"); });
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Excepción capturada: {ex.Message}");
        }
    }
}

En este ejemplo, la excepción lanzada dentro del Task es capturada por el bloque try-catch que envuelve el await.

Ejecución de múltiples tasks

C# proporciona dos métodos muy útiles para trabajar con múltiples Tasks al mismo tiempo: Task.WhenAll y Task.WhenAny.

Task.WhenAll

Este método permite ejecutar varios Tasks de forma concurrente y espera hasta que todos se completen.

using System;
using System.Threading.Tasks;

public class Program
{
    public static async Task Main()
    {
        Task task1 = Task.Delay(2000);
        Task task2 = Task.Delay(3000);

        await Task.WhenAll(task1, task2); // Espera a que ambas tareas terminen
        Console.WriteLine("Ambas tareas completadas.");
    }
}

Task.WhenAny

Task.WhenAny permite continuar la ejecución tan pronto como cualquiera de los Tasks haya terminado. Este método es útil cuando se necesita la respuesta más rápida de entre varias operaciones.

using System;
using System.Threading.Tasks;

public class Program
{
    public static async Task Main()
    {
        Task task1 = Task.Delay(2000);
        Task task2 = Task.Delay(3000);

        await Task.WhenAny(task1, task2); // Continúa cuando una tarea termine
        Console.WriteLine("Al menos una tarea completada.");
    }
}