CodeGym /Kursy /C# SELF /Anulowanie operacji asynchronicznych

Anulowanie operacji asynchronicznych

C# SELF
Poziom 42 , Lekcja 4
Dostępny

1. Wprowadzenie

Kiedy uruchamiasz operację asynchroniczną (lub długotrwałą), użytkownik (albo inny kod) może nagle zdecydować: "Stop! Już nie trzeba! Zatrzymaj się!". Na przykład użytkownik przerwał pobieranie ogromnego pliku, zamknął okno programu lub zmienił zdanie o wyszukiwaniu informacji w dużej bazie danych. Bez obsługi anulowania Twoja aplikacja może dalej działać i pochłaniać zasoby na darmo — to nie najlepszy sposób dbania o użytkownika i komputer.

Typowe scenariusze anulowania:

  • Anulowanie pobierania pliku lub anulowanie wysyłania danych.
  • Szybkie wyjście z ciężkiego przetwarzania danych na żądanie użytkownika.
  • W razie nagłego wstrzymania/zatrzymania długiej pracy, która już nie jest aktualna.

Anulowanie — Twój sekretny składnik do tworzenia przyjaznych, responsywnych i oszczędnych aplikacji.

Jak anulować operacje asynchroniczne w .NET?

W .NET do anulowania długotrwałych zadań używa się koncepcji "tokenu anulowania" (CancellationToken). To specjalny obiekt, który przekazywany jest do wszystkich komponentów zadania. Jeśli ktoś poprosi o anulowanie operacji — token od razu to przekazuje wszystkim zainteresowanym częściom programu. W praktyce to działa jak czerwony sztandar: kto pierwszy zobaczy — ten się zatrzyma.

W .NET mechanizm ten jest zrealizowany przy pomocy dwóch kluczowych klas:

Ważne: sam token anulowania nie przerywa wykonywania kodu, tylko "sygnałuje" — a Twoja aplikacja decyduje, kiedy i jak zareagować na ten sygnał.

2. Tworzymy token anulowania i anulujemy zadanie

Pokażemy, jak to działa, na prostym przykładzie (rozwiniemy nasze edukacyjne konsolowe demo).

Przykład: prosta operacja asynchroniczna z anulowaniem


using System;
using System.Threading;
using System.Threading.Tasks;

namespace DemoApp
{
    class Program
    {
        static async Task Main()
        {
            // Tworzymy źródło tokenu anulowania
            CancellationTokenSource cts = new CancellationTokenSource();

            // Uruchamiamy zadanie asynchroniczne
            Task longRunningTask = DoWorkAsync(cts.Token);

            Console.WriteLine("Naciśnij dowolny klawisz, żeby anulować operację...");
            Console.ReadKey();

            // Żądamy anulowania
            cts.Cancel();

            try
            {
                await longRunningTask;
            }
            catch (OperationCanceledException)
            {
                Console.WriteLine("Operacja została anulowana!");
            }
        }

        // Metoda asynchroniczna wspierająca anulowanie
        static async Task DoWorkAsync(CancellationToken cancellationToken)
        {
            for (int i = 0; i < 10; i++)
            {
                // Sprawdzamy sygnał anulowania
                cancellationToken.ThrowIfCancellationRequested();

                Console.WriteLine($"Wykonywanie kroku {i + 1}/10...");
                await Task.Delay(1000); // Opóźnienie 1 sekunda
            }

            Console.WriteLine("Operacja zakończona pomyślnie!");
        }
    }
}

Jak to działa?

- Tworzymy CancellationTokenSource (cts), skąd bierzemy token (cts.Token).
- Przekazujemy ten token do naszej asynchronicznej operacji.
- Wewnątrz DoWorkAsync() regularnie sprawdzamy token na anulowanie metodą ThrowIfCancellationRequested(). Jeśli użytkownik poprosi o anulowanie zadania — metoda rzuci wyjątek OperationCanceledException, i zadanie zostanie przerwane.
- W Main() czekamy na naciśnięcie klawisza i wywołujemy cts.Cancel(), żeby "zasygnalizować" potrzebę zatrzymania operacji.

Jeśli nie będziesz sprawdzać cancellationToken.IsCancellationRequested lub nie wywołasz ThrowIfCancellationRequested(), Twoje zadanie będzie dalej działać, jak gdyby nic się nie stało — token to tylko informacyjny sztandar.

3. CancellationToken: jak to działa? I trochę magii

Token anulowania to obiekt, który łatwo przekazać między metodami i zadaniami. To daje dużą elastyczność:

  • Ten sam token można użyć w kilku operacjach asynchronicznych i synchronicznych.
  • Można zorganizować "grupowe" anulowanie dla wszystkich zadań, jeśli kilka operacji korzysta z tokenu z jednego CancellationTokenSource.
  • Token anulowania jest dyskretny: nawet jeśli go zignorujesz, kod będzie działać dalej jak wcześniej.

Zarządzanie anulowaniem: gdzie i jak sprawdzać token?

Sprawdzać, czy został podniesiony "sztandar anulowania", można i trzeba tam, gdzie to logicznie uzasadnione: w pętli, na każdym kroku długiego przetwarzania, przy przejściu między etapami itp.


// W dowolnym miejscu sprawdzenia
if (cancellationToken.IsCancellationRequested)
{
    Console.WriteLine("Operacja anulowana! Wychodzimy...");
    return;
}

// Albo tak (krótko i z rzuconym wyjątkiem)
cancellationToken.ThrowIfCancellationRequested();

Zwykle używa się ThrowIfCancellationRequested() — on rzuca specjalny wyjątek, który można złapać w kodzie wywołującym.

4. Metody asynchroniczne biblioteki standardowej

Wiele klas i metod .NET (szczególnie asynchronicznych) obsługuje CancellationToken od razu "po wyjęciu z pudełka". Warto ich używać, żeby poprawnie zatrzymywać operacje.

Oto przykład z asynchronicznym czytaniem pliku:


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)
        {
            // Przetwarzamy dane...
            // Jeśli token anulowania został zażądany, ReadAsync sam rzuci OperationCanceledException
        }
    }
}

Więcej o FileStream.ReadAsync i CancellationToken przeczytasz w oficjalnej dokumentacji.

5. Przydatne niuanse

Timeout — to też anulowanie!

Możesz automatycznie anulować operacje po upływie określonego czasu. Do tego źródło tokenu można "zaprogramować":


// Utworzyć CancellationTokenSource z timeoutem (np. 5 sekund)
CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));

Po 5 sekundach token zostanie automatycznie "podniesiony", i wszystkie operacje z nim powiązane zatrzymają się przy następnym sprawdzeniu. To wygodne, gdy nie chcesz czekać w nieskończoność.

Co się dzieje przy anulowaniu?

Kiedy wywołujesz Cancel() na źródle tokenu, wszystkie metody które używają tego tokenu i sprawdzają jego stan dowiedzą się o tej zmianie. Ale jeśli Twój kod nie sprawdza tokenu — anulowanie nie nastąpi.

Typowy błąd: zapomnieć przekazać token do wszystkich asynchronicznych i długich operacji. Wtedy część operacji zostanie anulowana, a część będzie dalej działać, jakby nic się nie stało.

Wizualizacja: jak przebiega anulowanie

sequenceDiagram
    participant Main as Główny wątek
    participant CTS as CancellationTokenSource
    participant Task as Zadanie asynchroniczne

    Main->>CTS: tworzy CTS, dostaje token
    Main->>Task: przekazuje token do zadania asynchronicznego
    Note over Task: Zadanie okresowo sprawdza token
    Main->>CTS: wywołuje Cancel()
    CTS-->>Task: token dostaje status "anulowano"
    Task-->>Main: rzuca OperationCanceledException
    Main->>Main: łapie wyjątek i kończy działanie

Gdzie stosuje się anulowanie operacji asynchronicznych?

  • Asynchroniczne pobierania i zapytania do serwera: można anulować przy słabym internecie lub zniecierpliwieniu użytkownika.
  • Duże obliczenia: można zatrzymać po timeoutcie lub na życzenie użytkownika.
  • Operacje sieciowe, praca z plikami, przetwarzanie dużych kolekcji w tle.

Na tym kończymy wprowadzenie do anulowania operacji asynchronicznych — teraz Twoja aplikacja będzie nie tylko szybka, ale i troskliwa!

6. Wskazówki i typowe błędy

Nie zapominaj przekazywać tokenu anulowania do wszystkich metod i wywołań, które obsługują cancellation. Jeśli gdzieś nie przekażesz tokenu — operacja może "zawiesić się" i nie zatrzymać.

Sprawdzaj token regularnie — szczególnie w długich pętlach, przy przetwarzaniu plików lub przy pobieraniu dużych ilości danych. Używaj IsCancellationRequested lub ThrowIfCancellationRequested().

Nie próbuj "na siłę" kończyć wątku lub zadania z zewnątrz: token anulowania to prośba o zatrzymanie, a nie młotek na wątek.

Funkcje biblioteki standardowej, takie jak ReadAsync, Delay, HttpClient.SendAsync i wiele innych, już wspierają anulowanie przez token. Korzystaj z tego!

Przy obsłudze anulowania łap konkretnie OperationCanceledException — to specjalny wyjątek mówiący o poprawnym anulowaniu na żądanie.

1
Ankieta/quiz
Asynchroniczne operacje na plikach, poziom 42, lekcja 4
Niedostępny
Asynchroniczne operacje na plikach
Zalety asynchronicznej pracy z plikami
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION