CodeGym /Cursos /C# SELF /Cancelación de operaciones:

Cancelación de operaciones: CancellationToken

C# SELF
Nivel 60 , Lección 4
Disponible

1. Introducción a CancellationToken

Imagínate la situación: lanzas una descarga larga de un archivo y de repente recuerdas que tu plan de Internet tiene límite y cada gigabyte cuesta. O el usuario se confundió, inició un cálculo y luego decidió que no lo necesita. Quiere pulsar «Cancelar» y no esperar a que termine. Las aplicaciones modernas deben ser reactivas, y para eso tenemos que poder enviar una señal de parada en cualquier momento y terminar correctamente una operación asíncrona.

Para eso en .NET existe toda una infraestructura de cancelación — el CancellationToken.

CancellationToken (literalmente "token de cancelación") es un objeto especial que pasas dentro de tu operación larga. En cualquier momento puedes señalar la cancelación a través de CancellationTokenSource, y la operación debe comprobar periódicamente el token (por ejemplo, IsCancellationRequested) y terminar correctamente, si hace falta llamando a ThrowIfCancellationRequested().

¿Cómo está hecho? (Breve)

  • Hay un objeto CancellationTokenSource que "genera" tokens y sabe cancelarlos (método Cancel()).
  • El propio CancellationToken es una "bandera de señalización" que puedes dar a muchas operaciones (a través de la propiedad Token del source).
  • La operación comprueba periódicamente el token: si la cancelación fue solicitada, sale del trabajo (o lanza OperationCanceledException).

Analogía: tú eres el jefe (tú eres el CancellationTokenSource). Das a tus empleados "placas" (estos son los CancellationToken). Cuando decides que toca cancelar, levantas la bandera roja — y todos los que tienen placa se retiran inmediatamente, dejando el almuerzo a medias.

2. Cómo usar CancellationToken

Crear un source del token de cancelación (CancellationTokenSource)

var cts = new CancellationTokenSource();

Obtener el token (CancellationToken)

CancellationToken token = cts.Token;

Pasar el token a un método asíncrono

La mayoría de los métodos asíncronos estándar de .NET aceptan un parámetro de tipo CancellationToken. Por ejemplo, HttpClient.GetAsync, Stream.ReadAsync, Task.Delay, etc.

Ejemplo — espera con cancelación:

await Task.Delay(10000, token); // Esperar 10 segundos — ¡pero se puede cancelar!

Solicitar la cancelación (por botón o por temporizador)

cts.Cancel(); // Todas las operaciones que recibieron este token sabrán de la cancelación

Comprobar el token dentro del método

Dentro de tus métodos (especialmente si el trabajo es largo y cíclico) necesitas comprobar regularmente la bandera de cancelación y lanzar la excepción OperationCanceledException si se solicitó la cancelación:

token.ThrowIfCancellationRequested();

O simplemente comprobar la propiedad:

if (token.IsCancellationRequested)
{
    // Liberamos recursos, salimos del método
}

3. Ejemplo: Añadimos cancelación a nuestra app de aprendizaje

Supongamos que tenemos una app que descarga datos desde un sitio. Vamos a añadir la posibilidad de cancelar la descarga si el usuario cambia de opinión.

Ejemplo básico de descarga asíncrona

using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

class Downloader
{
    public async Task DownloadAsync(string url)
    {
        var client = new HttpClient();
        string content = await client.GetStringAsync(url); // Sin cancelación
        Console.WriteLine("¡Descarga completada!");
    }
}

Añadiendo CancellationToken

public async Task DownloadAsync(string url, CancellationToken token)
{
    var client = new HttpClient();
    string content = await client.GetStringAsync(url, token); // ¡Ahora con soporte de cancelación!
    Console.WriteLine("¡Descarga completada!");
}

Controlando la cancelación desde el código que llama

static async Task Main(string[] args)
{
    var downloader = new Downloader();
    var cts = new CancellationTokenSource();

    Console.WriteLine("Introduce la URL para descargar:");
    string url = Console.ReadLine();

    var downloadTask = downloader.DownloadAsync(url, cts.Token);

    Console.WriteLine("Pulsa cualquier tecla para cancelar la descarga...");
    Console.ReadKey();

    cts.Cancel(); // Señal de cancelación

    try
    {
        await downloadTask;
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine("¡Descarga cancelada por el usuario!");
    }
}

¡Así de simple! Ahora el usuario puede interrumpir la operación en cualquier momento.

4. Interacción entre CancellationTokenSource y los métodos

flowchart TD
    A["Código del usuario (Main)"] -- crea --> B["CancellationTokenSource"]
    B -- emite --> C["CancellationToken"]
    C -- se pasa a --> D["Operación asíncrona"]
    A -- llama a Cancel() --> B
    D -- comprueba periódicamente --> C
    C -- informa de la cancelación --> D
    D -- lanza Exception o termina trabajo --> A

5. Manejo de la cancelación

Cuando una operación recibe un token de cancelación, hay dos comportamientos posibles:

Los métodos de .NET lanzan la excepción por sí mismos.
Si llamas a métodos estándar, como Stream.ReadAsync, HttpClient.GetAsync o Task.Delay, y les pasas el token, — en cuanto se llame a Cancel(), esos métodos lanzarán por sí mismos OperationCanceledException. Solo te queda capturar esa excepción.

Código asíncrono personalizado.
Si implementas tú mismo una operación "larga", por ejemplo procesamiento cíclico o cálculos pesados, es tu responsabilidad comprobar regularmente token.IsCancellationRequested (o llamar token.ThrowIfCancellationRequested()) para reaccionar correctamente a la cancelación.

Ejemplo: operación "larga" con comprobación manual de cancelación

public async Task CalculatePrimesAsync(int max, CancellationToken token)
{
    for (int i = 2; i < max; i++)
    {
        token.ThrowIfCancellationRequested(); // Comprobamos cancelación

        if (IsPrime(i))
        {
            Console.WriteLine($"Número primo: {i}");
            await Task.Delay(100, token); // Damos "descanso" (se puede cancelar)
        }
    }
    Console.WriteLine("¡Cálculo completado!");
}

private bool IsPrime(int n)
{
    for (int i = 2; i <= Math.Sqrt(n); i++)
        if (n % i == 0) return false;
    return true;
}

6. Matices útiles

Métodos y clases que soportan CancellationToken

Clase/método ¿Soporta CancellationToken? Ejemplo de uso
Task.Delay
await Task.Delay(1000, token);
HttpClient.GetAsync
await client.GetAsync(url, token);
Stream.ReadAsync
await stream.ReadAsync(buffer, 0, len, token);
Task.Run
Task.Run(() => ..., token);
File.ReadAllTextAsync
await File.ReadAllTextAsync(path, token);
Thread.Sleep
No lo soporta; es mejor usar Task.Delay
Tus métodos ✔ (¡si añades soporte!)
MyLongMethod(token)

Ciclo de vida de una operación cancelable

sequenceDiagram
    participant Usuario
    participant Main
    participant CancellationTokenSource
    participant OperaciónAsíncrona

    Usuario->>Main: Lanzar operación
    Main->>CancellationTokenSource: Crear CTS
    Main->>OperaciónAsíncrona: Lanzar y pasar CancellationToken
    Usuario->>Main: Pulsa "Cancelar"
    Main->>CancellationTokenSource: Llama a Cancel()
    OperaciónAsíncrona->>OperaciónAsíncrona: Nota la cancelación (\nIsCancellationRequested)
    OperaciónAsíncrona-->>Main: Lanza OperationCanceledException
    Main->>Usuario: Muestra mensaje "Operación cancelada"

Dónde se usa esto en la vida real

  • Apps UI: Interrumpir descargas largas, cálculos, trabajo con archivos si el usuario quiere cerrar la ventana o cancelar la acción.
  • Apps de servidor: Si el cliente corta la conexión — mejor cancelar el procesamiento de la petición para no malgastar recursos.
  • Procesamiento de datos grandes: Las tareas pueden ser muy largas — siempre hay que dar la posibilidad de parar cálculos o migraciones.
  • Integración con hardware: Escaneos, impresión u otras operaciones a veces deben ser abortadas de emergencia — el soporte de cancelación es obligatorio.

7. Errores típicos al trabajar con CancellationToken

Error #1: Ignorar la comprobación del token.
Si la operación no comprueba token.IsCancellationRequested o no llama a ThrowIfCancellationRequested(), no se detendrá al cancelar y seguirá consumiendo recursos.

Error #2: Manejo incorrecto de OperationCanceledException.
Si no capturas OperationCanceledException, la aplicación puede terminar inesperadamente. Siempre usa try-catch para manejar la cancelación.

Error #3: Gestión inadecuada de recursos al cancelar.
La cancelación no revierte cambios automáticamente (por ejemplo, en archivos o bases de datos). Debes limpiar recursos manualmente en el bloque de catch.

Error #4: Pasar un token ya cancelado.
Si el token ya está cancelado, el método lanzará la excepción inmediatamente, lo que puede romper la lógica si no lo has previsto.

2
Tarea
C# SELF, nivel 60, lección 4
Bloqueada
Búsqueda de números primos con soporte para cancelación
Búsqueda de números primos con soporte para cancelación
1
Cuestionario/control
Comparación de Task y Thread, nivel 60, lección 4
No disponible
Comparación de Task y Thread
Procesamiento paralelo de datos
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION