← Назад к вопросам

Что такое CancellationToken и как правильно отменять асинхронные операции в C#?

2.2 Middle🔥 201 комментариев
#Асинхронность и многопоточность

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое 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 — это фундаментальный механизм для создания отзывчивых и надёжных приложений. Правильная его реализация предотвращает "подвисания", экономит ресурсы и улучшает пользовательский опыт, особенно в распределённых системах и веб-приложениях.

Что такое CancellationToken и как правильно отменять асинхронные операции в C#? | PrepBro