1. Introdução ao CancellationToken
Imagine a situação: você inicia um download grande e de repente lembra que seu plano de Internet é limitado e cada gigabyte custa caro. Ou o usuário se confundiu, começou um cálculo e depois resolveu que não precisa mais. Ele quer apertar «Cancelar» e não esperar até o fim. Aplicativos modernos precisam ser responsivos, e para isso devemos conseguir enviar um sinal de parada a qualquer momento e interromper corretamente a operação assíncrona.
Para isso, no .NET existe toda uma infraestrutura de cancelamento — o CancellationToken.
CancellationToken (literalmente "token de cancelamento") é um objeto especial que você passa para dentro da sua operação longa. A qualquer momento você pode sinalizar o cancelamento através do CancellationTokenSource, e a operação deve periodicamente checar o token (por exemplo, IsCancellationRequested) e terminar corretamente, se necessário chamando ThrowIfCancellationRequested().
Como ele é por dentro? (Resumo)
- Existe o objeto CancellationTokenSource, que "gera" tokens e sabe cancelá-los (método Cancel()).
- O próprio CancellationToken é uma "bandeira de sinalização" que pode ser distribuída para várias operações (via propriedade Token do source).
- A operação checa periodicamente o token: se o cancelamento foi requisitado, ela para o trabalho (ou lança OperationCanceledException).
Analogia: você é o chefe (você é o CancellationTokenSource). Você distribui crachás aos seus subordinados (esses são os CancellationToken). Quando você decide que é hora de cancelar tudo, você levanta a bandeira vermelha — e todos que têm crachá recuam imediatamente, deixando o almoço pela metade.
2. Como usar o CancellationToken
Criar uma source do token de cancelamento (CancellationTokenSource)
var cts = new CancellationTokenSource();
Obter o próprio token (CancellationToken)
CancellationToken token = cts.Token;
Passar o token para um método assíncrono
A maioria dos métodos assíncronos padrão do .NET aceita um parâmetro do tipo CancellationToken. Por exemplo, HttpClient.GetAsync, Stream.ReadAsync, Task.Delay etc.
Exemplo — delay com cancelamento:
await Task.Delay(10000, token); // Esperar 10 segundos — mas pode ser cancelado!
Solicitar cancelamento (por exemplo, por botão ou por timer)
cts.Cancel(); // Todas as operações que receberam esse token vão saber do cancelamento
Checar o token dentro do método
Dentro dos seus métodos (especialmente se o trabalho for longo e cíclico) é necessário verificar regularmente a flag de cancelamento e lançar a exceção OperationCanceledException se o cancelamento foi pedido:
token.ThrowIfCancellationRequested();
Ou simplesmente checar a propriedade:
if (token.IsCancellationRequested)
{
// Liberamos recursos, saímos do método
}
3. Exemplo: Vamos adicionar cancelamento ao nosso app de estudo
Suponha que temos um app que baixa dados de um site. Vamos adicionar a possibilidade de cancelar o download se o usuário mudar de ideia.
Exemplo básico de download assíncrono
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); // Sem cancelamento
Console.WriteLine("Download concluído!");
}
}
Adicionando CancellationToken
public async Task DownloadAsync(string url, CancellationToken token)
{
var client = new HttpClient();
string content = await client.GetStringAsync(url, token); // Agora com suporte a cancelamento!
Console.WriteLine("Download concluído!");
}
Controlando o cancelamento do código chamador
static async Task Main(string[] args)
{
var downloader = new Downloader();
var cts = new CancellationTokenSource();
Console.WriteLine("Digite a URL para download:");
string url = Console.ReadLine();
var downloadTask = downloader.DownloadAsync(url, cts.Token);
Console.WriteLine("Pressione qualquer tecla para cancelar o download...");
Console.ReadKey();
cts.Cancel(); // Sinal de cancelamento
try
{
await downloadTask;
}
catch (OperationCanceledException)
{
Console.WriteLine("Download cancelado pelo usuário!");
}
}
É simples assim! Agora o usuário pode interromper a operação a qualquer momento.
4. Interação entre CancellationTokenSource e métodos
flowchart TD
A["Código do usuário (Main)"] -- cria --> B["CancellationTokenSource"]
B -- emite --> C["CancellationToken"]
C -- é passado para --> D["Operação assíncrona"]
A -- chama Cancel() --> B
D -- checa periodicamente --> C
C -- sinaliza o cancelamento para --> D
D -- lança Exception ou termina o trabalho --> A
5. Tratamento do cancelamento
Quando a operação recebe um token de cancelamento, existem duas formas de comportamento:
Métodos .NET lançam a exceção por conta própria.
Se você chamar métodos padrão, como Stream.ReadAsync, HttpClient.GetAsync ou Task.Delay, e passar o token, — assim que Cancel() for chamado, esses métodos vão lançar OperationCanceledException por conta própria. Só resta você capturar essa exceção.
Código assíncrono customizado.
Se você implementar uma operação longa por conta própria, por exemplo processamento em loop ou cálculos pesados, é sua responsabilidade checar regularmente token.IsCancellationRequested (ou chamar token.ThrowIfCancellationRequested()) para reagir ao cancelamento corretamente.
Exemplo: operação longa com verificação manual de cancelamento
public async Task CalculatePrimesAsync(int max, CancellationToken token)
{
for (int i = 2; i < max; i++)
{
token.ThrowIfCancellationRequested(); // Checa o cancelamento
if (IsPrime(i))
{
Console.WriteLine($"Número primo: {i}");
await Task.Delay(100, token); // Dá um "descanso" (pode ser cancelado)
}
}
Console.WriteLine("Cálculo concluído!");
}
private bool IsPrime(int n)
{
for (int i = 2; i <= Math.Sqrt(n); i++)
if (n % i == 0) return false;
return true;
}
6. Dicas úteis
Métodos e classes que suportam CancellationToken
| Classe/método | Suporta CancellationToken? | Exemplo de uso |
|---|---|---|
|
✔ | |
|
✔ | |
|
✔ | |
|
✔ | |
|
✔ | |
|
✖ | Não suporta; é melhor usar Task.Delay |
| Seus métodos | ✔ (se você adicionar suporte!) | |
Lifecycle de uma operação cancelável
sequenceDiagram
participant Usuário
participant Main
participant CancellationTokenSource
participant OperaçãoAssíncrona
Usuário->>Main: Inicia operação
Main->>CancellationTokenSource: Cria CTS
Main->>OperaçãoAssíncrona: Inicia e passa o CancellationToken
Usuário->>Main: Aperta "Cancelar"
Main->>CancellationTokenSource: Chama Cancel()
OperaçãoAssíncrona->>OperaçãoAssíncrona: Nota o cancelamento (\nIsCancellationRequested)
OperaçãoAssíncrona-->>Main: Lança OperationCanceledException
Main->>Usuário: Mostra mensagem "Operação cancelada"
Onde isso é usado na prática
- Aplicações UI: Interromper downloads longos, cálculos, trabalho com arquivos se o usuário quiser fechar a janela ou cancelar a ação.
- Aplicações servidoras: Se o cliente cair a conexão — é melhor cancelar o processamento do request para não desperdiçar recursos.
- Processamento de grandes volumes: Tarefas podem levar muito tempo — sempre dê a opção de parar cálculos ou migrações.
- Integração com hardware: Scans, impressão e outras operações às vezes precisam ser abortadas urgentemente — suporte a cancelamento é obrigatório.
7. Erros típicos ao trabalhar com CancellationToken
Erro №1: Ignorar a checagem do token.
Se a operação não checa token.IsCancellationRequested ou não chama ThrowIfCancellationRequested(), ela não vai parar quando cancelarem, consumindo recursos desnecessários.
Erro №2: Tratamento incorreto de OperationCanceledException.
Se você não capturar OperationCanceledException, o app pode terminar de forma inesperada. Sempre use try-catch para lidar com cancelamento.
Erro №3: Gerenciamento errado de recursos ao cancelar.
O cancelamento não desfaz mudanças automaticamente (por exemplo, em arquivos ou banco de dados). É preciso limpar recursos manualmente no bloco catch.
Erro №4: Passar um token já cancelado.
Se o token já estiver cancelado, o método vai lançar a exceção imediatamente, o que pode quebrar a lógica se isso não for previsto.
GO TO FULL VERSION