CSharp

Tutorial CSharp: Tareas

CSharp tareas: planificación y manejo. Domina la planificación y manejo de tareas en CSharp con ejemplos prácticos y detallados.

En C#, una Task o tarea representa una operación asíncrona. Es una de las formas centrales en las que se implementa la programación asincrónica en .NET, lo que ayuda a mantener las aplicaciones responsivas. Las tareas se encuentran en el namespace System.Threading.Tasks.

using System.Threading.Tasks;

Creación de una Tarea

Una tarea se puede crear utilizando el método Task.Factory.StartNew. Por ejemplo:

Task task = Task.Factory.StartNew(() =>
{
    // Código de la tarea
});

El bloque de código entre las llaves { } es lo que se ejecutará en la nueva tarea.

También puede asignarse la tarea a un método:

void MyMethod()
{
    // Código de la tarea
}

Task task = Task.Factory.StartNew(MyMethod);

Esperar a una Tarea

Para esperar a que una tarea se complete, se puede utilizar el método Wait:

task.Wait();

O el operador await si se está dentro de un método asíncrono:

await task;

Tareas con Resultado

Para crear una tarea que devuelva un resultado, se puede utilizar Task<T>. El tipo T es el tipo de dato que devuelve la tarea. Para devolver un valor desde una tarea, se utiliza Task.FromResult:

Task<int> task = Task.FromResult(10);

Para obtener el resultado de una tarea, se utiliza la propiedad Result:

int result = task.Result;

Por ejemplo, puede definir una tarea que devuelva la suma de dos números:

Task<int> SumTask(int a, int b)
{
    return Task.FromResult(a + b);
}

Task<int> task = SumTask(1, 2);
int sum = task.Result; // sum será 3

Manejo de Excepciones en Tareas

Las excepciones que se lanzan dentro de una tarea se propagan al código que está esperando la tarea. Si se está utilizando Wait, la excepción se propaga cuando se llama a Wait. Si se está utilizando await, la excepción se propaga cuando el control de la ejecución retorna al método asíncrono.

Task task = Task.Factory.StartNew(() =>
{
    throw new Exception("Error en la tarea");
});

try
{
    task.Wait();
}
catch (AggregateException ex)
{
    foreach (var innerEx in ex.InnerExceptions)
    {
        Console.WriteLine(innerEx.Message);
    }
}

Tareas Anidadas

Una tarea puede iniciar otras tareas. Estas tareas secundarias no son esperadas automáticamente por la tarea principal, a menos que se utilice TaskCreationOptions.AttachedToParent.

Task task = Task.Factory.StartNew(() =>
{
    // Esta tarea es independiente
    Task.Factory.StartNew(() =>
    {
        // Código de la tarea
    });

    // Esta tarea se adjunta a la tarea principal
    Task.Factory.StartNew(() =>
    {
        // Código de la tarea
    }, TaskCreationOptions.AttachedToParent);
});

Continuaciones

Las continuaciones permiten especificar una tarea que se ejecutará cuando una tarea anterior se haya completado. Para crear una continuación, se utiliza el método ContinueWith:

Task task1 = Task.Factory.StartNew(() =>
{
    // Código de la tarea 1
});

Task task2 = task1.ContinueWith((t) =>
{
    // Código de la tarea 2
});

En el código anterior, task2 se ejecutará después de que task1 se haya completado.

Tareas Asíncronas

A partir de C# 5.0, el lenguaje introdujo una sintaxis simplificada para trabajar con tareas, usando las palabras clave async y await.

Para crear un método asíncrono que devuelve una tarea, se utiliza la palabra clave async antes del tipo de retorno del método, y se utiliza la palabra clave Task como tipo de retorno. En el método, se puede utilizar await para esperar a que se complete una tarea.

async Task MyAsyncMethod()
{
    await Task.Delay(1000); // Espera 1 segundo
}

Un método asíncrono también puede devolver un valor. En este caso, se utiliza Task<T> como tipo de retorno:

async Task<int> SumAsync(int a, int b)
{
    await Task.Delay(1000);
    return a + b;
}

Para llamar a un método asíncrono y obtener su resultado, se utiliza await:

int sum = await SumAsync(1, 2);

Cancelación de Tareas

La cancelación de tareas en C# se realiza a través de la clase CancellationTokenSource y la estructura CancellationToken. Un CancellationToken se puede pasar a una tarea para permitir que la tarea compruebe si se ha solicitado una cancelación y termine anticipadamente.

CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
CancellationToken token = cancellationTokenSource.Token;

Task task = Task.Run(() =>
{
    while (true)
    {
        // Comprueba si se ha solicitado la cancelación
        if (token.IsCancellationRequested)
        {
            // Lanza una OperationCanceledException para terminar la tarea
            throw new OperationCanceledException(token);
        }

        // Código de la tarea
    }
}, token);

// Solicita la cancelación de la tarea
cancellationTokenSource.Cancel();

Es importante recordar que la cancelación en .NET es cooperativa, lo que significa que la tarea debe comprobar el token de cancelación y terminar por sí misma. No se puede forzar a una tarea a terminar.

Sincronización de Tareas

En C#, hay varias formas de sincronizar tareas, es decir, de coordinar la ejecución de múltiples tareas.

Esperar Múltiples Tareas

Se puede utilizar Task.WaitAll para esperar a que todas las tareas se completen:

Task task1 = Task.Run(() => { /* Código de la tarea 1 */ });
Task task2 = Task.Run(() => { /* Código de la tarea 2 */ });

Task.WaitAll(task1, task2);

O Task.WhenAll si se está en un método asíncrono:

await Task.WhenAll(task1, task2);

Si solo se quiere esperar a que se complete al menos una tarea, se puede utilizar Task.WaitAny:

Task.WaitAny(task1, task2);

O Task.WhenAny si se está en un método asíncrono:

await Task.WhenAny(task1, task2);

Exclusión Mutua

Para garantizar que solo una tarea puede ejecutar cierto bloque de código a la vez, se puede utilizar lock:

object lockObject = new object();

Task task1 = Task.Run(() =>
{
    lock (lockObject)
    {
        // Solo una tarea puede ejecutar este código a la vez
    }
});

Task task2 = Task.Run(() =>
{
    lock (lockObject)
    {
        // Solo una tarea puede ejecutar este código a la vez
    }
});

También se puede utilizar la clase Mutex para lo mismo, pero Mutex puede trabajar entre varios procesos, mientras que lock solo funciona dentro del mismo proceso.

Semáforos

Un semáforo es una variable que se utiliza para controlar el acceso a un recurso común por múltiples procesos en un entorno de concurrencia mientras evita la condición de carrera y la inanición.

SemaphoreSlim semaphore = new SemaphoreSlim(2); // Permite que dos tareas accedan a la vez

Task task1 = Task.Run(async () =>
{
    await semaphore.WaitAsync();

    try
    {
        // Solo dos tareas pueden ejecutar este código a la vez
    }
    finally
    {
        semaphore.Release();
    }
});

Task task2 = Task.Run(async () =>
{
    await semaphore.WaitAsync();

    try
    {
        // Solo dos tareas pueden ejecutar este código a la vez
    }
    finally
    {
        semaphore.Release();
    }
});

Las técnicas de sincronización son una parte esencial de la programación de tareas, pero es importante recordar que pueden introducir complejidades adicionales y riesgos de bloqueos, por lo que deben utilizarse con precaución.

Certifícate en CSharp con CertiDevs PLUS

Ejercicios de esta lección Tareas

Evalúa tus conocimientos de esta lección Tareas con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Todas las lecciones de CSharp

Accede a todas las lecciones de CSharp y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

En esta lección

Objetivos de aprendizaje de esta lección

  1. Comprender qué es una Task y cómo se utiliza para realizar operaciones asincrónicas en C#.
  2. Aprender a crear tareas utilizando Task.Factory.StartNew y asignándolas a métodos.
  3. Saber cómo esperar a que una tarea se complete utilizando Wait o await.
  4. Entender cómo trabajar con tareas que devuelven un resultado utilizando Task<T> y Task.FromResult.
  5. Conocer cómo manejar excepciones que ocurren dentro de tareas.
  6. Aprender sobre tareas anidadas y continuaciones para crear secuencias de tareas.
  7. Familiarizarse con la programación asíncrona utilizando las palabras clave async y await.
  8. Saber cómo cancelar tareas utilizando CancellationTokenSource y CancellationToken.
  9. Conocer técnicas de sincronización, como lock y semáforos, para coordinar múltiples tareas.