CodeGym /Kursy /C# SELF /Anulowanie operacji: Cance...

Anulowanie operacji: CancellationToken

C# SELF
Poziom 60, Lekcja 4
Dostępny

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
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
Nie obsługuje; lepiej użyć Task.Delay
Twoje metody ✔ (jeśli dodasz obsługę!)
MyLongMethod(token)

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.

2
Zadanie
C# SELF, poziom 60, lekcja 4
Niedostępne
Wyszukiwanie liczb pierwszych z obsługą anulowania
Wyszukiwanie liczb pierwszych z obsługą anulowania
1
Ankieta/quiz
Porównanie Task i Thread, poziom 60, lekcja 4
Niedostępny
Porównanie Task i Thread
Przetwarzanie równoległe danych
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION