Как реализуется отмена задач с использованием CancellationToken в C#
Что проверяют
Понимание, что отмена в .NET кооперативная: токен ничего не прерывает сам, его должен проверять или принимать сам код. Смотрят на связку CancellationTokenSource и CancellationToken, на прокидывание токена вглубь вызовов и на корректную обработку OperationCanceledException.
Как отвечать
Схема: источник владеет отменой, токен раздаётся потребителям, потребители обязаны токен уважать.
using var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(2));
try
{
await DoWorkAsync(cts.Token);
}
catch (OperationCanceledException) when (cts.IsCancellationRequested)
{
Console.WriteLine("Задача отменена");
}
static async Task DoWorkAsync(CancellationToken token)
{
for (int i = 0; i < 10; i++)
{
token.ThrowIfCancellationRequested();
Console.WriteLine($"Шаг {i + 1}/10");
await Task.Delay(1000, token);
}
}Что тут принципиально:
- Токен передаётся в каждый асинхронный вызов, а не только проверяется в цикле.
Task.Delay(1000)без токена досидит свою секунду даже после отмены,Task.Delay(1000, token)отвалится сразу. То же сHttpClient.SendAsync,SqlCommand.ExecuteReaderAsync,Channel.ReadAsync. ThrowIfCancellationRequested()бросаетOperationCanceledExceptionи нужен там, где нет ожиданий: в теле долгого CPU-цикла, между пачками работы. Проверятьtoken.IsCancellationRequestedи молча выходить с обычным результатом нельзя: вызывающий решит, что работа завершена успешно.- Ловим
OperationCanceledException, а неTaskCanceledException. Второй наследуется от первого, и если ловить только его, отмена изThrowIfCancellationRequestedпролетит мимоcatch. Фильтрwhenотличает свою отмену от чужой (таймаут вложенного HTTP-вызова тоже прилетитOperationCanceledException). CancellationTokenSourceреализуетIDisposable, и его надо освобождать: уCancelAfterпод капотом таймер, а зарегистрированные колбэки держат ссылки.using varзакрывает вопрос.CancellationTokenSource.CreateLinkedTokenSource(a, b)объединяет отмену по таймауту и отмену пользователем,CancellationToken.Noneподставляется, когда отмена не нужна.token.Register(callback)пригодится для освобождения ресурсов, которые сами токен не понимают; регистрацию надо диспозить.- В ASP.NET Core токен запроса уже есть: параметр
CancellationTokenв методе контроллера биндится автоматически изHttpContext.RequestAbortedи отменяется, когда клиент разорвал соединение. - В .NET 8 появился
await cts.CancelAsync(), полезный когда колбэки отмены долгие и не хочется блокировать вызывающий поток.
Где спотыкаются
Принимают токен в сигнатуре и не передают его дальше, так что метод формально «поддерживает отмену», а на деле досчитывает до конца. Второе: глотают OperationCanceledException в общем catch (Exception) вместе с настоящими ошибками, из-за чего отменённая задача выглядит как успешно выполненная.