Підрозділ 16.5
Скасування завдань. CancellationToken
16.5. Скасування завдань. CancellationToken У реальних системах завдання нерідко потрібно зупинити достроково: користувач натиснув «Скасувати», сплив тайм аут, виникла аварійна ситуація або система перевантажен
16.5. Скасування завдань. CancellationToken
У реальних системах завдання нерідко потрібно зупинити достроково: користувач натиснув «Скасувати», сплив тайм-аут, виникла аварійна ситуація або система перевантажена. Примусове завершення потоку через Thread.Abort() давно застаріло і вилучено з .NET 5+: воно небезпечне, бо залишає ресурси неприбраними. Правильний підхід — кооперативне скасування через CancellationToken.
Концепція кооперативного скасування полягає в тому, що завдання саме перевіряє, чи не надійшов сигнал скасування, і коректно завершується у зручний момент — після звільнення ресурсів, запису незавершеного результату, закриття з'єднань. Жодного примусового переривання — лише добровільна відповідь на запит.
Ключові типи
| Тип | Роль |
|---|---|
CancellationTokenSource |
Джерело сигналу скасування. Тільки він може викликати Cancel() |
CancellationToken |
Токен — «квиток» з інформацією про скасування. Передається у завдання |
OperationCanceledException |
Виняток, що сигналізує про кооперативне скасування |
Розподіл ролей чіткий: той хто управляє (зовнішній код) тримає CancellationTokenSource і вирішує, коли скасувати. Завдання отримує лише CancellationToken — воно може лише перевірити стан, але не може само себе скасувати через чужий джерело.

Спосіб 1: м'яке скасування через IsCancellationRequested
Найпростіший підхід — завдання регулярно перевіряє token.IsCancellationRequested і виходить через return при виявленні сигналу. Стан завдання після цього — RanToCompletion (успішне завершення), оскільки виняток не кидався:
М'яке скасування підходить для довгих циклічних операцій, де є зручна точка перевірки між ітераціями.
Спосіб 2: жорстке скасування через ThrowIfCancellationRequested
token.ThrowIfCancellationRequested() кидає OperationCanceledException, якщо надійшов сигнал скасування. Завдання переходить у стан Canceled. При виклику Wait() або Result зовнішній код отримає AggregateException з TaskCanceledException всередині:
Жорстке скасування підходить для задач, де після отримання сигналу скасування продовжувати виконання немає сенсу і завдання може бути перервано в будь-якій точці перевірки.
Перевірка стану: IsCanceled vs IsFaulted
Реєстрація обробника: Register()
CancellationToken.Register() дозволяє зареєструвати делегат, який буде викликаний при скасуванні. Це аналог події скасування — корисний для звільнення ресурсів, логування або надсилання повідомлень:
Обробник Register() викликається синхронно в тому потоці, що викликав Cancel(). Якщо зареєстровано кілька обробників, вони викликаються у порядку реєстрації.
CancelAfter — автоматичне скасування за тайм-аутом
CancellationTokenSource.CancelAfter(ms) автоматично надсилає сигнал скасування через вказану кількість мілісекунд. Це стандартний спосіб реалізації тайм-аутів:
Скасування у Parallel.For і Parallel.ForEach
CancellationToken передається у Parallel через ParallelOptions. При скасуванні Parallel зупиняє нові ітерації і кидає OperationCanceledException:
Пов'язані токени (Linked tokens)
Іноді потрібно об'єднати кілька джерел скасування: наприклад, зовнішній тайм-аут або натискання «Скасувати». CancellationTokenSource.CreateLinkedTokenSource створює новий токен, що спрацьовує при скасуванні будь-якого з переданих:
Пов'язані токени — потужний інструмент для реалізації складних стратегій скасування, де кілька незалежних подій можуть ініціювати зупинку однієї операції. Після завершення їх потрібно утилізувати через Dispose() (або using), щоб звільнити ресурси.