JavaRush /Курси /C# SELF /Скасування асинхронних операцій

Скасування асинхронних операцій

C# SELF
Рівень 42 , Лекція 4
Відкрита

1. Вступ

Коли ви запускаєте асинхронну (або тривалу) операцію, користувач (або інший код) може раптом вирішити: «Стій! Уже не треба! Зупиніться!». Наприклад, користувач захотів перервати завантаження великого файлу, закрив вікно програми або передумав шукати інформацію у великій базі даних. Без підтримки скасування ваша програма може й далі працювати й марно споживати ресурси — не найкраща турбота ні про користувача, ні про комп’ютер.

Типові сценарії скасування:

  • Скасування завантаження файлу або надсилання даних.
  • Швидкий вихід зі складної обробки даних на вимогу користувача.
  • Пауза або зупинення тривалого завдання, яке вже неактуальне.

Скасування — ваш секретний інгредієнт для дружніх, чуйних і ощадливих застосунків.

Як скасовувати асинхронні операції в .NET?

У .NET для скасування тривалих завдань використовують концепцію «токена скасування» (CancellationToken). Це спеціальний об’єкт, який передається в усі компоненти завдання. Якщо хтось попросив скасувати операцію, токен миттєво повідомляє про це всі зацікавлені частини програми. Фактично це як червоний прапорець: хто першим його побачить — той і зупиняється.

У .NET цей механізм реалізовано за допомогою двох ключових класів:

  • CancellationTokenSource — створює токен скасування й керує ним.
  • CancellationToken — передається в асинхронні методи, щоб їх можна було скасувати.

Важливо: сам по собі токен скасування не перериває виконання коду, він лише «сигналізує», а вже ваш застосунок сам вирішує, коли і як реагувати на цей сигнал.

2. Створюємо токен скасування і скасовуємо завдання

Розгляньмо, як це працює, на простому прикладі (продовжимо наше навчальне консольне застосування).

Приклад: проста асинхронна операція зі скасуванням


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

namespace DemoApp
{
    class Program
    {
        static async Task Main()
        {
            // Створюємо джерело токена скасування
            CancellationTokenSource cts = new CancellationTokenSource();

            // Запускаємо асинхронне завдання
            Task longRunningTask = DoWorkAsync(cts.Token);

            Console.WriteLine("Натисніть будь-яку клавішу, щоб скасувати операцію...");
            Console.ReadKey();

            // Запитуємо скасування
            cts.Cancel();

            try
            {
                await longRunningTask;
            }
            catch (OperationCanceledException)
            {
                Console.WriteLine("Операцію скасовано!");
            }
        }

        // Асинхронний метод, що підтримує скасування
        static async Task DoWorkAsync(CancellationToken cancellationToken)
        {
            for (int i = 0; i < 10; i++)
            {
                // Перевіряємо сигнал скасування
                cancellationToken.ThrowIfCancellationRequested();

                Console.WriteLine($"Виконання кроку {i + 1}/10...");
                await Task.Delay(1000); // Затримка 1 секунду
            }

            Console.WriteLine("Операцію успішно завершено!");
        }
    }
}

Як це працює?

- Ми створюємо CancellationTokenSource (cts) і звідти отримуємо токен (cts.Token).
- Передаємо цей токен у нашу асинхронну операцію.
- Усередині DoWorkAsync() регулярно перевіряємо токен на скасування методом ThrowIfCancellationRequested(). Якщо користувач попросить скасувати завдання, метод кине виняток OperationCanceledException, і завдання перерветься.
- У Main() чекаємо натискання будь-якої клавіші й викликаємо cts.Cancel(), щоб надіслати сигнал про зупинку операції.

Якщо ви не перевірятимете cancellationToken.IsCancellationRequested або не викличете ThrowIfCancellationRequested(), ваше завдання продовжить працювати, наче нічого не сталося, — токен лише інформаційний прапорець.

3. CancellationToken: як це влаштовано? І трохи магії

Токен скасування — це об’єкт, який легко передати між методами й завданнями. Це дає багато гнучкості:

  • Один і той самий токен можна використовувати в кількох асинхронних і синхронних операціях.
  • Можна організувати «групове» скасування одразу для всіх завдань, якщо кілька операцій використовують токен з одного CancellationTokenSource.
  • Токен скасування ненав’язливий: навіть якщо ви його ігноруєте, код і далі працюватиме як раніше.

Керування скасуванням: де й як перевіряти токен?

Перевіряти, чи піднято «прапорець скасування», слід там, де це логічно: у циклі, на кожному кроці тривалої обробки, під час переходу між етапами тощо.


// У будь-який момент перевірки
if (cancellationToken.IsCancellationRequested)
{
    Console.WriteLine("Операцію скасовано! Завершуємо роботу...");
    return;
}

// Або так (коротко і з киданням винятку)
cancellationToken.ThrowIfCancellationRequested();

Зазвичай використовують ThrowIfCancellationRequested() — він кидає спеціальний виняток, який можна перехопити в коді, який викликає.

4. Асинхронні методи стандартної бібліотеки

Багато класів і методів .NET (особливо асинхронних) підтримують CancellationToken без додаткових налаштувань. Їх можна і треба використовувати, щоб коректно зупиняти операції.

Ось приклад з асинхронним читанням файлу:


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)
        {
            // Обробляємо дані...
            // Якщо токен скасування було запитано, ReadAsync сам кине OperationCanceledException
        }
    }
}

Докладніше про FileStream.ReadAsync і CancellationToken — в офіційній документації.

5. Корисні нюанси

Тайм-аут — це теж скасування!

Ви можете автоматично скасовувати операції після визначеного часу. Для цього токен скасування можна «запрограмувати»:


// Створити CancellationTokenSource з тайм-аутом (наприклад, 5 секунд)
CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));

Через 5 с токен автоматично позначиться як скасований, і всі операції з ним зупиняться під час наступної перевірки. Це дуже зручно, якщо ви не хочете чекати вічно.

Що відбувається під час скасування?

Коли ви викликаєте Cancel() на джерелі токена, усі методи, що використовують цей токен і перевіряють його стан, дізнаються про зміну. Але якщо ваш код не перевіряє токен — скасування не станеться.

Типова помилка — забути передати токен у всі асинхронні та тривалі операції. Тоді частину буде скасовано, а частина продовжить працювати, наче нічого не сталося.

Візуалізація: як відбувається скасування

sequenceDiagram
    participant Main as Головний потік
    participant CTS as CancellationTokenSource
    participant Task as Асинхронне завдання

    Main->>CTS: створює CTS, отримує токен
    Main->>Task: передає токен в асинхронне завдання
    Note over Task: Завдання періодично перевіряє токен
    Main->>CTS: викликає Cancel()
    CTS-->>Task: токен отримує статус "скасовано"
    Task-->>Main: кидає OperationCanceledException
    Main->>Main: перехоплює виняток і завершує роботу

Де застосовується скасування асинхронних операцій?

  • Асинхронні завантаження й запити до сервера: можна скасувати за поганого з’єднання з інтернетом або коли користувач втомився чекати.
  • Великі обчислення: можна зупинити за тайм-аутом або на бажання користувача.
  • Мережеві операції, робота з файлами, обробка великих колекцій у фоновому режимі.

На цьому знайомство зі скасуванням асинхронних операцій завершено — тепер ваш застосунок буде не лише швидким, а й турботливим!

6. Поради та типові помилки

Не забувайте передавати токен скасування у всі методи та виклики, що підтримують скасування. Якщо десь не передасте токен — операція може «зависнути» і не зупинитися.

Перевіряйте токен регулярно — особливо в довгих циклах, під час обробки файлів або завантаження великих обсягів даних. Використовуйте IsCancellationRequested або ThrowIfCancellationRequested().

Не намагайтеся примусово завершувати потік чи завдання ззовні: токен скасування — це ввічливий запит на зупинку, а не насильницьке припинення.

Функції стандартної бібліотеки, такі як ReadAsync, Delay, HttpClient.SendAsync та багато інших, уже підтримують скасування через токен. Користуйтеся цим!

Під час обробки скасування перехоплюйте саме OperationCanceledException — це спеціальний виняток, що інформує про коректне скасування на запит.

1
Опитування
Асинхронні операції з файлами, рівень 42, лекція 4
Недоступний
Асинхронні операції з файлами
Переваги асинхронної роботи з файлами
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ