Что такое CancellationToken и как правильно отменять асинхронные операции в C#?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое CancellationToken?
CancellationToken — это структура в .NET, предоставляющая механизм отмены асинхронных или длительных операций. Он реализует паттерн кооперативной отмены (cooperative cancellation), где задача не принудительно прерывается, а периодически проверяет запрос на отмену и корректно завершает работу. Это ключевой инструмент для управления жизненным циклом операций, особенно в асинхронном и многопоточном коде.
Основные свойства и методы:
- IsCancellationRequested — флаг, указывающий, запрошена ли отмена.
- ThrowIfCancellationRequested() — генерирует
OperationCanceledException, если отмена запрошена. - Register() — позволяет зарегистрировать callback, выполняемый при отмене.
Он работает в паре с CancellationTokenSource, который создаёт токены и инициирует отмену. Один CancellationTokenSource может порождать несколько токенов, обеспечивая централизованное управление.
// Создание источника отмены и токена
var cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
// Запуск отмены
cts.Cancel();
Как правильно отменять асинхронные операции?
1. Передача токена в асинхронный метод
Токен должен передаваться в метод, поддерживающий отмену. Большинство стандартных .NET API принимают CancellationToken (например, HttpClient, Task.Delay, EF Core).
public async Task ProcessDataAsync(CancellationToken cancellationToken)
{
// Проверка отмены в начале операции
cancellationToken.ThrowIfCancellationRequested();
// Имитация работы с периодической проверкой
for (int i = 0; i < 100; i++)
{
cancellationToken.ThrowIfCancellationRequested();
await Task.Delay(100, cancellationToken); // Задержка с поддержкой отмены
}
}
2. Использование ThrowIfCancellationRequested
Этот метод — основной способ реакции на отмену: он генерирует исключение, которое должно всплывать вверх по стеку вызовов, уведомляя о прекращении операции.
3. Регистрация callback-ов через Register
Если нужно выполнить cleanup-логику (закрытие файлов, освобождение ресурсов) при отмене, используйте Register. Это гарантирует выполнение действий даже при прерывании.
public async Task DownloadFileAsync(string url, CancellationToken cancellationToken)
{
using var client = new HttpClient();
// Регистрация callback для принудительного завершения загрузки при отмене
using var registration = cancellationToken.Register(() => client.CancelPendingRequests());
var response = await client.GetAsync(url, cancellationToken);
// ... обработка ответа
}
4. Объединение токенов через CancellationTokenSource.CreateLinkedTokenSource
Позволяет создать токен, который отменяется при срабатывании любого из исходных токенов. Полезно в сценариях с таймаутами или множественными условиями отмены.
public async Task ExecuteWithTimeoutAsync(CancellationToken userToken, int timeoutMs)
{
using var timeoutCts = new CancellationTokenSource(timeoutMs);
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(userToken, timeoutCts.Token);
await ProcessDataAsync(linkedCts.Token);
}
5. Обработка OperationCanceledException
При отмене код генерирует OperationCanceledException (или производный TaskCanceledException). Его следует обрабатывать на верхнем уровне, чтобы отличить штатную отмену от ошибок.
try
{
await ProcessDataAsync(cancellationToken);
}
catch (OperationCanceledException)
{
Console.WriteLine("Операция отменена.");
// Возможно, логирование или уведомление пользователя
}
6. Использование таймаутов
CancellationTokenSource поддерживает автоматическую отмену через заданное время с помощью конструктора или метода CancelAfter.
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); // Отмена через 30 секунд
// или
cts.CancelAfter(5000); // Отмена через 5 секунд
7. Отмена нескольких операций
Один CancellationTokenSource позволяет координировать отмену нескольких задач одновременно:
var cts = new CancellationTokenSource();
var tasks = new List<Task>();
for (int i = 0; i < 5; i++)
{
tasks.Add(RunWorkerAsync(cts.Token));
}
// Единовременная отмена всех задач
cts.Cancel();
Важные практики и рекомендации
- Всегда передавайте токен явно в поддерживающие его методы. Не храните токен в статических полях.
- Проверяйте отмену в циклах и длительных операциях — но не слишком часто, чтобы не снижать производительность.
- Используйте токен для отмены, а не для контроля бизнес-логики (например, не используйте
IsCancellationRequestedкак флаг завершения). - Освобождайте CancellationTokenSource через
Dispose()илиusing, особенно при работе сLinkedTokenSource, чтобы избежать утечек памяти. - Асинхронные операции должны оставаться отменяемыми — избегайте блокирующих вызовов, которые игнорируют токен (например,
Task.Wait()без токена). - В библиотечных методах делайте токен опциональным через параметр по умолчанию:
CancellationToken cancellationToken = default.
CancellationToken — это фундаментальный механизм для создания отзывчивых и надёжных приложений. Правильная его реализация предотвращает "подвисания", экономит ресурсы и улучшает пользовательский опыт, особенно в распределённых системах и веб-приложениях.