1. Poznajemy CancellationToken
Wyobraź sobie sytuację: uruchamiasz długie pobieranie pliku, a potem przypominasz sobie, że masz limitowany Internet i gigabajty są na wagę złota. Albo użytkownik się pomylił, uruchomił obliczenia, a potem stwierdził, że mu to niepotrzebne. Chce kliknąć „Anuluj” i nie czekać do końca. Nowoczesne aplikacje powinny być responsywne, więc musimy umieć przesłać sygnał zatrzymania w dowolnym momencie i poprawnie przerwać wykonanie operacji asynchronicznej.
Do tego właśnie w .NET istnieje cała infrastruktura anulowania — CancellationToken.
CancellationToken (dosłownie „token anulowania”) to specjalny obiekt, który przekazujesz do swojej długotrwałej operacji. W dowolnym momencie możesz zasygnalizować anulowanie przez CancellationTokenSource, a operacja powinna okresowo sprawdzać token (np. IsCancellationRequested) i poprawnie zakończyć działanie, jeśli trzeba wywołując ThrowIfCancellationRequested().
Jak to działa? (Krótko)
- Jest obiekt CancellationTokenSource, który „generuje” tokeny i potrafi je anulować (metoda Cancel()).
- Sam CancellationToken to „sygnałowy znacznik”, który możesz rozdać wielu operacjom (przez właściwość Token źródła).
- Operacja okresowo sprawdza token: jeśli anulowanie zostało żądane, przerywa pracę (albo rzuca OperationCanceledException).
Analogia: jesteś szefem (ty to CancellationTokenSource). Rozdajesz pracownikom „identyfikatory” (to CancellationToken). Gdy decydujesz, że czas anulować wszystko, podnosisz czerwony sztandar — i wszyscy, którzy mają identyfikator, natychmiast się wycofują, zostawiając niedokończony obiad.
2. Jak używać CancellationToken
Utworzyć źródło tokena anulowania (CancellationTokenSource)
var cts = new CancellationTokenSource();
Pobrać sam token (CancellationToken)
CancellationToken token = cts.Token;
Przekazać token do metody asynchronicznej
Większość standardowych metod asynchronicznych .NET przyjmuje parametr typu CancellationToken. Na przykład, HttpClient.GetAsync, Stream.ReadAsync, Task.Delay i inne.
Przykład — opóźnienie z anulowaniem:
await Task.Delay(10000, token); // Czekaj 10 sekund — ale można anulować!
Zadanie anulowania (np. przez przycisk lub timer)
cts.Cancel(); // Wszystkie operacje, które dostały ten token, dowiedzą się o anulowaniu
Sprawdzać token wewnątrz metody
Wewnątrz swoich metod (szczególnie jeśli praca jest długa i cykliczna) trzeba regularnie sprawdzać flagę anulowania i rzucać wyjątek OperationCanceledException, jeśli anulowanie jest żądane:
token.ThrowIfCancellationRequested();
Albo po prostu sprawdzać właściwość:
if (token.IsCancellationRequested)
{
// Zwolnić zasoby, wyjść z metody
}
3. Przykład: Dodajmy anulowanie do naszej aplikacji szkoleniowej
Załóżmy, że mamy aplikację, która pobiera dane ze strony. Dodajmy możliwość anulowania pobierania, jeśli użytkownik zmieni zdanie.
Podstawowy przykład asynchronicznego pobierania
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); // Bez anulowania
Console.WriteLine("Pobieranie zakończone!");
}
}
Dokładamy CancellationToken
public async Task DownloadAsync(string url, CancellationToken token)
{
var client = new HttpClient();
string content = await client.GetStringAsync(url, token); // Teraz z obsługą anulowania!
Console.WriteLine("Pobieranie zakończone!");
}
Zarządzamy anulowaniem po stronie wywołującego
static async Task Main(string[] args)
{
var downloader = new Downloader();
var cts = new CancellationTokenSource();
Console.WriteLine("Wpisz URL do pobrania:");
string url = Console.ReadLine();
var downloadTask = downloader.DownloadAsync(url, cts.Token);
Console.WriteLine("Naciśnij dowolny klawisz, aby anulować pobieranie...");
Console.ReadKey();
cts.Cancel(); // Sygnał anulowania
try
{
await downloadTask;
}
catch (OperationCanceledException)
{
Console.WriteLine("Pobieranie anulowane przez użytkownika!");
}
}
To takie proste! Teraz użytkownik może przerwać operację w dowolnym momencie.
4. Interakcja CancellationTokenSource i metod
flowchart TD
A["Kod użytkownika (Main)"] -- tworzy --> B["CancellationTokenSource"]
B -- wydaje --> C["CancellationToken"]
C -- przekazywany --> D["Operacja asynchroniczna"]
A -- wywołuje Cancel() --> B
D -- okresowo sprawdza --> C
C -- informuje o anulowaniu --> D
D -- rzuca Exception lub kończy pracę --> A
5. Obsługa anulowania
Gdy operacja otrzyma token anulowania, są dwa sposoby zachowania:
Metody .NET same rzucą wyjątek.
Jeśli wywołujesz standardowe metody, jak Stream.ReadAsync, HttpClient.GetAsync lub Task.Delay, i przekazujesz im token — gdy zostanie wywołane Cancel(), te metody same rzucą OperationCanceledException. Tobie zostaje tylko przechwycić ten wyjątek.
Własny kod asynchroniczny.
Jeżeli sam implementujesz „długą” operację, np. pętlę lub ciężkie obliczenia, twoim obowiązkiem jest regularnie sprawdzać token.IsCancellationRequested (lub wywołać token.ThrowIfCancellationRequested()), żeby poprawnie reagować na anulowanie.
Przykład: „Długa” operacja z ręcznym sprawdzaniem anulowania
public async Task CalculatePrimesAsync(int max, CancellationToken token)
{
for (int i = 2; i < max; i++)
{
token.ThrowIfCancellationRequested(); // Sprawdzamy anulowanie
if (IsPrime(i))
{
Console.WriteLine($"Liczba pierwsza: {i}");
await Task.Delay(100, token); // Dajemy "odpocząć" (można anulować)
}
}
Console.WriteLine("Obliczenia zakończone!");
}
private bool IsPrime(int n)
{
for (int i = 2; i <= Math.Sqrt(n); i++)
if (n % i == 0) return false;
return true;
}
6. Przydatne niuanse
Metody i klasy, które obsługują CancellationToken
| Klasa/metoda | Obsługuje CancellationToken? | Przykład użycia |
|---|---|---|
|
✔ | |
|
✔ | |
|
✔ | |
|
✔ | |
|
✔ | |
|
✖ | Nie obsługuje; lepiej użyć Task.Delay |
| Twoje metody | ✔ (jeśli dodasz obsługę!) | |
Cykl życia operacji możliwej do anulowania
sequenceDiagram
participant Użytkownik
participant Main
participant CancellationTokenSource
participant OperacjaAsynchroniczna
Użytkownik->>Main: Uruchamia operację
Main->>CancellationTokenSource: Tworzy CTS
Main->>OperacjaAsynchroniczna: Uruchamia i przekazuje CancellationToken
Użytkownik->>Main: Klika "Anuluj"
Main->>CancellationTokenSource: Wywołuje Cancel()
OperacjaAsynchroniczna->>OperacjaAsynchroniczna: Zauważa anulowanie (\nIsCancellationRequested)
OperacjaAsynchroniczna-->>Main: Rzuca OperationCanceledException
Main->>Użytkownik: Pokazuje komunikat "Operacja anulowana"
Gdzie to używane w praktyce
- Aplikacje UI: Przerwać długie pobierania, obliczenia, operacje na plikach, jeśli użytkownik zamknie okno lub anulować akcję.
- Aplikacje serwerowe: Jeśli klient przerwał połączenie — lepiej od razu anulować przetwarzanie żądania, żeby nie marnować zasobów.
- Przetwarzanie dużych danych: Zadania mogą być bardzo długie — zawsze warto dać możliwość zatrzymania obliczeń lub migracji.
- Integracja ze sprzętem: Skanowanie, druk i inne operacje czasem trzeba szybko przerwać — obsługa anulowania jest tu konieczna.
7. Typowe błędy przy pracy z CancellationToken
Błąd nr 1: Ignorowanie sprawdzania tokena.
Jeśli operacja nie sprawdza token.IsCancellationRequested ani nie wywołuje ThrowIfCancellationRequested(), nie zatrzyma się po anulowaniu i będzie marnować zasoby.
Błąd nr 2: Nieprawidłowa obsługa OperationCanceledException.
Jeśli nie przechwycisz OperationCanceledException, aplikacja może się zakończyć nieoczekiwanie. Zawsze używaj try-catch do obsługi anulowania.
Błąd nr 3: Niepoprawne zarządzanie zasobami przy anulowaniu.
Anulowanie nie cofa zmian automatycznie (np. w plikach czy bazach danych). Musisz ręcznie posprzątać zasoby w bloku catch.
Błąd nr 4: Przekazywanie już anulowanego tokena.
Jeśli token jest już anulowany, metoda od razu rzuci wyjątek, co może zaburzyć logikę, jeśli tego nie przewidziano.
GO TO FULL VERSION