OOP Course
Сьогодні

Підрозділ 16.5

Скасування завдань. CancellationToken

16.5. Скасування завдань. CancellationToken У реальних системах завдання нерідко потрібно зупинити достроково: користувач натиснув «Скасувати», сплив тайм аут, виникла аварійна ситуація або система перевантажен

16.5. Скасування завдань. CancellationToken

У реальних системах завдання нерідко потрібно зупинити достроково: користувач натиснув «Скасувати», сплив тайм-аут, виникла аварійна ситуація або система перевантажена. Примусове завершення потоку через Thread.Abort() давно застаріло і вилучено з .NET 5+: воно небезпечне, бо залишає ресурси неприбраними. Правильний підхід — кооперативне скасування через CancellationToken.

Концепція кооперативного скасування полягає в тому, що завдання саме перевіряє, чи не надійшов сигнал скасування, і коректно завершується у зручний момент — після звільнення ресурсів, запису незавершеного результату, закриття з'єднань. Жодного примусового переривання — лише добровільна відповідь на запит.

Ключові типи

Тип Роль
CancellationTokenSource Джерело сигналу скасування. Тільки він може викликати Cancel()
CancellationToken Токен — «квиток» з інформацією про скасування. Передається у завдання
OperationCanceledException Виняток, що сигналізує про кооперативне скасування

Розподіл ролей чіткий: той хто управляє (зовнішній код) тримає CancellationTokenSource і вирішує, коли скасувати. Завдання отримує лише CancellationToken — воно може лише перевірити стан, але не може само себе скасувати через чужий джерело.

Механізм скасування завдань: 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), щоб звільнити ресурси.

Розроблено Tomka Yurii · © 2026 ·