1. Introduzione
Quando avvii un'operazione asincrona (o lunga), l'utente (o un altro pezzo di codice) può improvvisamente decidere: "Aspetta! Non serve più! Fermati!". Per esempio, l'utente può interrompere il download di un file enorme, chiudere la finestra del programma o cambiare idea sulla ricerca in un grande database. Senza supporto per la cancellazione la tua applicazione può continuare a lavorare e consumare risorse inutilmente — non il massimo per l'esperienza utente né per il sistema.
Scenari tipici di cancellazione:
- Annullamento del download di un file o dell'invio di dati.
- Uscita rapida da un'elaborazione complessa su richiesta dell'utente.
- Interruzione improvvisa di un compito lungo che non è più rilevante.
La cancellazione è il tuo ingrediente segreto per applicazioni reattive, rispettose e parsimoniose.
Come annullare operazioni asincrone in .NET?
In .NET per annullare compiti lunghi si usa il concetto del "token di cancellazione" (CancellationToken). È un oggetto speciale che viene passato a tutte le componenti di un'operazione. Se qualcuno richiede la cancellazione — il token segnala immediatamente a tutte le parti interessate. In pratica è come una bandierina rossa: chi la vede per primo si ferma.
In .NET questo meccanismo è realizzato con due classi chiave:
- CancellationTokenSource — crea e gestisce il token di cancellazione.
- CancellationToken — viene passato ai metodi asincroni per permetterne l'annullamento.
Importante: il token di cancellazione di per sé non interrompe l'esecuzione del codice, si limita a "segnalare" — poi l'applicazione decide come e quando reagire a quel segnale.
2. Creiamo un token di cancellazione e annulliamo un task
Vediamo come funziona con un esempio semplice (svilupperemo la nostra app console di studio).
Esempio: operazione asincrona semplice con cancellazione
using System;
using System.Threading;
using System.Threading.Tasks;
namespace DemoApp
{
class Program
{
static async Task Main()
{
// Creiamo la source del token di cancellazione
CancellationTokenSource cts = new CancellationTokenSource();
// Avviamo il task asincrono
Task longRunningTask = DoWorkAsync(cts.Token);
Console.WriteLine("Premi un tasto per annullare l'operazione...");
Console.ReadKey();
// Richiediamo la cancellazione
cts.Cancel();
try
{
await longRunningTask;
}
catch (OperationCanceledException)
{
Console.WriteLine("L'operazione è stata annullata!");
}
}
// Metodo asincrono che supporta la cancellazione
static async Task DoWorkAsync(CancellationToken cancellationToken)
{
for (int i = 0; i < 10; i++)
{
// Controlliamo il segnale di cancellazione
cancellationToken.ThrowIfCancellationRequested();
Console.WriteLine($"Esecuzione passo {i + 1}/10...");
await Task.Delay(1000); // Ritardo di 1 secondo
}
Console.WriteLine("Operazione completata con successo!");
}
}
}
Come funziona?
- Creiamo CancellationTokenSource (cts), da cui otteniamo il token (cts.Token).
- Passiamo questo token alla nostra operazione asincrona.
- Dentro DoWorkAsync() controlliamo regolarmente il token con il metodo ThrowIfCancellationRequested(). Se l'utente richiede la cancellazione — il metodo lancerà l'eccezione OperationCanceledException e il task si interromperà.
- In Main() aspettiamo la pressione di un tasto e chiamiamo cts.Cancel() per "segnalare" la necessità di fermare l'operazione.
Se non controllerai cancellationToken.IsCancellationRequested o non chiamerai ThrowIfCancellationRequested(), il tuo task continuerà a girare come se nulla fosse — il token è solo una bandierina informativa.
3. CancellationToken: come è fatto? E un po' di magia
Il token di cancellazione è un oggetto che puoi passare facilmente tra metodi e task. Questo dà molta flessibilità:
- Lo stesso token può essere usato in più operazioni asincrone e sincrone.
- Puoi organizzare una "cancellazione di gruppo" per tutti i task che condividono il token di una singola CancellationTokenSource.
- Il token è non invasivo: anche se lo ignori, il codice continuerà a funzionare come prima.
Gestire la cancellazione: dove e come controllare il token?
Controllare se è stato alzato il "segnale di cancellazione" va fatto dove ha senso: in loop, ad ogni passo di una lunga elaborazione, al passaggio tra fasi ecc.
// In qualsiasi punto di controllo
if (cancellationToken.IsCancellationRequested)
{
Console.WriteLine("Operazione annullata! Esco...");
return;
}
// Oppure così (breve e con lancio di eccezione)
cancellationToken.ThrowIfCancellationRequested();
Di solito si usa ThrowIfCancellationRequested() — lancia un'eccezione speciale che può essere catturata nel codice chiamante.
4. Metodi asincroni della libreria standard
Molte classi e metodi .NET (soprattutto asincroni) supportano CancellationToken direttamente "out of the box". Usali per fermare le operazioni in modo "corretto".
Ecco un esempio con lettura asincrona da file:
using System.IO;
using System.Threading;
using System.Threading.Tasks;
class FileDemo
{
public static async Task ReadFileWithCancelAsync(string filePath, CancellationToken cancellationToken)
{
using FileStream stream = File.OpenRead(filePath);
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken)) > 0)
{
// Elaboriamo i dati...
// Se il token di cancellazione è stato richiesto, ReadAsync lancerà da solo OperationCanceledException
}
}
}
Per saperne di più su FileStream.ReadAsync e CancellationToken consulta la documentazione ufficiale.
5. Dettagli utili
Timeout — è anche cancellazione!
Puoi annullare automaticamente operazioni dopo un tempo prefissato. Per questo il CancellationTokenSource può essere "programmato":
// Creare CancellationTokenSource con timeout (per esempio, 5 secondi)
CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
Dopo 5 secondi il token sarà automaticamente "alzato" e tutte le operazioni con esso si fermeranno al prossimo controllo. Molto comodo se non vuoi aspettare all'infinito.
Cosa succede quando si annulla?
Quando chiami Cancel() sulla source del token, tutti i metodi che usano quel token e ne controllano lo stato vengono informati. Ma se il tuo codice non controlla il token — l'annullamento non avverrà.
Errore tipico: dimenticare di passare il token in tutti i metodi asincroni e lunghi. Allora una parte dell'operazione verrà annullata e un'altra continuerà a funzionare come se niente fosse.
Visualizzazione: come fluisce l'annullamento
sequenceDiagram
participant Main as Thread principale
participant CTS as CancellationTokenSource
participant Task as Task asincrono
Main->>CTS: crea CTS, ottiene token
Main->>Task: passa il token al task asincrono
Note over Task: Il task controlla periodicamente il token
Main->>CTS: chiama Cancel()
CTS-->>Task: il token riceve lo stato "cancellato"
Task-->>Main: lancia OperationCanceledException
Main->>Main: cattura l'eccezione e termina
Dove si applica la cancellazione delle operazioni asincrone?
- Download e richieste asincrone al server: puoi annullare con connessione instabile o se l'utente si stufa.
- Calcoli pesanti: puoi fermarli per timeout o su richiesta dell'utente.
- Operazioni di rete, lavoro con file, elaborazione di grandi collezioni in background.
Con questo la panoramica sull'annullamento delle operazioni asincrone è completa — la tua applicazione sarà non solo veloce, ma anche attenta!
6. Consigli ed errori tipici
Non dimenticare di passare il token di cancellazione a tutti i metodi e alle chiamate che supportano cancellation. Se lo perdi da qualche parte — l'operazione potrebbe "bloccarsi" e non fermarsi.
Controlla regolarmente il token — specialmente in loop lunghi, elaborazioni di file o download di grandi dimensioni. Usa IsCancellationRequested o ThrowIfCancellationRequested().
Non cercare di "terminare forzatamente" thread o task dall'esterno: il token di cancellazione è una richiesta di fermarsi, non una mazza per colpire il thread.
Le funzioni della libreria standard come ReadAsync, Delay, HttpClient.SendAsync e molte altre già supportano la cancellazione tramite token. Usale!
Quando gestisci la cancellazione cattura specificamente OperationCanceledException — è l'eccezione che indica una cancellazione corretta su richiesta.
GO TO FULL VERSION