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 |
|---|---|---|
|
✔ | |
|
✔ | |
|
✔ | |
|
✔ | |
|
✔ | |
|
✖ | No lo soporta; es mejor usar Task.Delay |
| Tus métodos | ✔ (¡si añades soporte!) | |
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.
GO TO FULL VERSION