1. Знайомство з CancellationToken
Уявіть ситуацію: ви запускаєте тривале завантаження файлу, а потім раптом згадуєте, що інтернет у вас за тарифним планом, і гігабайти на вагу золота. Або користувач запустив обчислення й згодом вирішив, що вони йому не потрібні. Він хоче натиснути «Скасувати» і не чекати завершення. Сучасні застосунки мають бути відгукливими, тож потрібно вміти передавати сигнал про зупинку будь-якої миті й коректно переривати виконання асинхронної операції.
Власне для цього у .NET і існує ціла інфраструктура скасування — CancellationToken.
CancellationToken (буквально «токен скасування») — це спеціальний об’єкт, який ви передаєте усередину своєї тривалої операції. У будь-який момент ви можете надіслати сигнал про скасування через CancellationTokenSource, а операція має періодично перевіряти токен (наприклад, IsCancellationRequested) і коректно завершуватися, за потреби викликаючи ThrowIfCancellationRequested().
Як це влаштовано (коротко)?
- Є об’єкт CancellationTokenSource, який «генерує» токени й уміє їх скасовувати (метод Cancel()).
- Власне CancellationToken — це «сигнальний прапорець», який можна передати багатьом операціям (через властивість Token у джерела).
- Операція періодично перевіряє токен: якщо скасування запитано, вона припиняє роботу (або викидає OperationCanceledException).
Аналогія: уявімо, що ви керівник (ви — це CancellationTokenSource). Ви роздаєте підлеглим «бейджики» (це CancellationToken). Коли вирішуєте все зупинити, піднімаєте червоний прапор — і всі, у кого є бейджик, одразу припиняють роботу.
2. Як використовувати CancellationToken
Створити джерело токена скасування (CancellationTokenSource)
var cts = new CancellationTokenSource();
Отримати сам токен (CancellationToken)
CancellationToken token = cts.Token;
Передати токен в асинхронний метод
Більшість стандартних асинхронних методів .NET приймають параметр типу CancellationToken. Наприклад, HttpClient.GetAsync, Stream.ReadAsync, Task.Delay тощо.
Приклад — затримка зі скасуванням:
await Task.Delay(10000, token); // Чекати 10 секунд — але можна скасувати!
Надіслати запит на скасування (наприклад, за кнопкою або таймером)
cts.Cancel(); // Усі операції, що отримали цей токен, дізнаються про скасування
Перевіряти токен усередині методу
Усередині власних методів (особливо якщо робота тривала й циклічна) слід регулярно перевіряти прапорець скасування і викидати виняток OperationCanceledException, якщо скасування запитано:
token.ThrowIfCancellationRequested();
Або просто перевіряти властивість:
if (token.IsCancellationRequested)
{
// Вивільняємо ресурси, виходимо з методу
}
3. Приклад: Додамо скасування в наш навчальний застосунок
Припустімо, у нас є застосунок, який завантажує дані з сайту. Додаймо туди можливість скасовувати завантаження, якщо користувач передумав.
Базовий приклад асинхронного завантаження
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); // Без скасування
Console.WriteLine("Завантаження завершено!");
}
}
Додаємо CancellationToken
public async Task DownloadAsync(string url, CancellationToken token)
{
var client = new HttpClient();
string content = await client.GetStringAsync(url, token); // Тепер з підтримкою скасування!
Console.WriteLine("Завантаження завершено!");
}
Керуємо скасуванням з боку коду, що викликає
static async Task Main(string[] args)
{
var downloader = new Downloader();
var cts = new CancellationTokenSource();
Console.WriteLine("Введіть URL-адресу для завантаження:");
string url = Console.ReadLine();
var downloadTask = downloader.DownloadAsync(url, cts.Token);
Console.WriteLine("Натисніть будь-яку клавішу, щоб скасувати завантаження...");
Console.ReadKey();
cts.Cancel(); // Сигнал скасування
try
{
await downloadTask;
}
catch (OperationCanceledException)
{
Console.WriteLine("Завантаження скасовано користувачем!");
}
}
Ось так просто! Тепер користувач може перервати операцію будь-якої миті.
4. Взаємодія CancellationTokenSource і методів
flowchart TD
A["Код користувача (Main)"] -- створює --> B["CancellationTokenSource"]
B -- видає --> C["CancellationToken"]
C -- передається --> D["Асинхронна операція"]
A -- викликає Cancel() --> B
D -- періодично перевіряє --> C
C -- повідомляє про скасування --> D
D -- кидає Exception або завершує роботу --> A
5. Обробка скасування
Коли операція отримує токен скасування, є два варіанти поведінки:
Методи .NET самостійно викинуть виняток.
Якщо ви викликаєте стандартні методи, як-от Stream.ReadAsync, HttpClient.GetAsync або Task.Delay, і передаєте їм токен, — щойно викличете Cancel(), ці методи самостійно викинуть OperationCanceledException. Вам лишається лише перехопити цей виняток.
Власний асинхронний код.
Якщо ви самі реалізуєте «довгу» операцію, наприклад, циклічну обробку чи складні обчислення, ваша відповідальність — регулярно перевіряти token.IsCancellationRequested (або викликати token.ThrowIfCancellationRequested()), щоб коректно реагувати на скасування.
Приклад: «Довга» операція з ручною перевіркою скасування
public async Task CalculatePrimesAsync(int max, CancellationToken token)
{
for (int i = 2; i < max; i++)
{
token.ThrowIfCancellationRequested(); // Перевіряємо скасування
if (IsPrime(i))
{
Console.WriteLine($"Просте число: {i}");
await Task.Delay(100, token); // Зробимо коротку паузу (можна скасувати)
}
}
Console.WriteLine("Обчислення завершено!");
}
private bool IsPrime(int n)
{
for (int i = 2; i <= Math.Sqrt(n); i++)
if (n % i == 0) return false;
return true;
}
6. Корисні нюанси
Методи й класи, що підтримують CancellationToken
| Клас/метод | Підтримує CancellationToken? | Приклад використання |
|---|---|---|
|
✔ | |
|
✔ | |
|
✔ | |
|
✔ | |
|
✔ | |
|
✖ | Не підтримує; краще використовувати Task.Delay |
| Ваші методи | ✔ (якщо додати підтримку!) | |
Життєвий цикл операції, яку можна скасувати
sequenceDiagram
participant Користувач
participant Main
participant CancellationTokenSource
participant Асинхронна операція
Користувач->>Main: Запуск операції
Main->>CancellationTokenSource: Створення CTS
Main->>Асинхронна операція: Запуск і передача CancellationToken
Користувач->>Main: Натискає "Скасувати"
Main->>CancellationTokenSource: Викликає Cancel()
Асинхронна операція->>Асинхронна операція: Помічає скасування (\nIsCancellationRequested)
Асинхронна операція-->>Main: Кидає OperationCanceledException
Main->>Користувач: Показує повідомлення "Операцію скасовано"
Де це використовується в реальному житті
- UI-застосунки: Перервати довгі завантаження, обчислення, роботу з файлами, якщо користувач захотів закрити вікно або скасувати дію.
- Серверні застосунки: Якщо клієнт обірвав з’єднання — краще одразу скасувати обробку запиту, щоб не витрачати ресурси.
- Обробка великих даних: Задачі можуть бути дуже тривалими — завжди треба давати можливість зупинити обчислення або міграції.
- Інтеграція з обладнанням: Сканування, друк та інші операції іноді потрібно екстрено перервати — підтримка скасування тут обов’язкова.
7. Типові помилки під час роботи з CancellationToken
Помилка №1: ігнорування перевірки токена.
Якщо операція не перевіряє token.IsCancellationRequested або не викликає ThrowIfCancellationRequested(), вона не зупиниться під час скасування, даремно витрачаючи ресурси.
Помилка №2: неправильна обробка OperationCanceledException.
Якщо не перехопити OperationCanceledException, застосунок може завершитися неочікувано. Завжди використовуйте try-catch для обробки скасування.
Помилка №3: некоректне керування ресурсами під час скасування.
Скасування не відкочує зміни автоматично (наприклад, у файлах чи базах даних). Потрібно вручну очищати ресурси в блоці catch.
Помилка №4: передавання вже скасованого токена.
Якщо токен уже скасовано, метод одразу викине виняток, що може порушити логіку, якщо це не враховано.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ