Что такое CancellationToken и для чего он нужен?

Ответ

CancellationToken — это структура в .NET, предназначенная для безопасной и кооперативной отмены долго выполняющихся или асинхронных операций. Он позволяет передать в метод сигнал о запросе на отмену, а методу — периодически проверять этот сигнал и корректно завершать работу.

Основные компоненты:

  1. CancellationTokenSource: Источник токена. Управляет состоянием отмены через метод Cancel().
  2. CancellationToken: Легковесная структура, которая только отражает состояние отмены. Передается в отменяемые методы.

Базовый пример использования:

public async Task ProcessDataAsync(CancellationToken cancellationToken)
{
    // 1. Периодическая ручная проверка
    for (int i = 0; i < 100; i++)
    {
        cancellationToken.ThrowIfCancellationRequested(); // Выбросит OperationCanceledException
        // ... выполнение работы на шаге i ...
        await Task.Delay(100, cancellationToken); // 2. Автоматическая проверка в поддерживающих API
    }
}

// Использование
public async Task MainMethod()
{
    using var cts = new CancellationTokenSource();
    // Запланировать отмену через 5 секунд, например, по таймауту
    cts.CancelAfter(TimeSpan.FromSeconds(5));

    try
    {
        await ProcessDataAsync(cts.Token);
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine("Операция была отменена.");
    }
}

Продвинутые сценарии:

  • Объединение токенов: Полезно, когда операция должна реагировать на несколько источников отмены (например, пользовательский запрос + общий таймаут приложения).
    
    var userCancellation = new CancellationTokenSource();
    var timeoutCancellation = new CancellationTokenSource(TimeSpan.FromSeconds(30));

using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource( userCancellation.Token, timeoutCancellation.Token );

await ProcessDataAsync(linkedCts.Token);


*   **Регистрация callback при отмене:**
```csharp
cancellationToken.Register(() => 
{
    Console.WriteLine("Токен отменен, выполняется очистка...");
    // Освобождение неуправляемых ресурсов и т.д.
});

Важные принципы:

  • Отмена является кооперативной. Метод должен сам проверять токен и корректно завершаться.
  • После вызова Cancel() соответствующий CancellationToken переходит в состояние "отменен" и больше не может быть использован заново.
  • Многие API .NET (например, Task.Delay, HttpClient.SendAsync, EF Core ToListAsync) имеют перегрузки, принимающие CancellationToken.

Ответ 18+ 🔞

Смотри, вот эта штука — CancellationToken — это типа такой вежливый, но настойчивый способ сказать какому-нибудь долбаному методу: «Эй, дружок, сворачивай лавочку, нам тут уже не надо».

Представь, ты запустил на компе архивацию терабайта фоток своей собаки, а потом передумал. Ну кнопки же «Отмена» нет, приходится процесс убивать через диспетчер. Так вот, CancellationToken — это и есть цивилизованная кнопка «Отмена» внутри твоего кода.

Из чего это говно состоит, прости Господи:

  1. CancellationTokenSource — это начальник, который орет «Отменить!». У него есть кнопка Cancel().
  2. CancellationToken — это бумажка, которую начальник дает рабочим (твоим методам). Рабочие смотрят на бумажку и видят, не написал ли начальник на ней «ВСЁ, ПИЗДЕЦ, ХВАТИТ».

Простой пример, чтоб было понятно даже мне вчерашнему:

public async Task КачаюМемыБесконечноAsync(CancellationToken cancellationToken)
{
    // 1. Рабочий (метод) время от времени смотрит на бумажку
    for (int i = 0; i < 1000000; i++)
    {
        cancellationToken.ThrowIfCancellationRequested(); // Если на бумажке "ПИЗДЕЦ" — выкидывает исключение и сваливает
        // ... тут он реально качает мемы ...
        await Task.Delay(100, cancellationToken); // 2. Или бумажку можно дать другому API, оно само проверит
    }
}

// А вот как этим пользоваться
public async Task MainMethod()
{
    using var cts = new CancellationTokenSource(); // Создаём начальника
    // Говорим ему: «Через 5 секунд накричи "ПИЗДЕЦ"»
    cts.CancelAfter(TimeSpan.FromSeconds(5));

    try
    {
        await КачаюМемыБесконечноAsync(cts.Token); // Даём рабочиму бумажку от этого начальника
    }
    catch (OperationCanceledException) // Ловим крик начальника в виде исключения
    {
        Console.WriteLine("Ну я отменил, чё бубнить-то.");
    }
}

А бывает и посложнее, вот смотри:

  • Токены-близнецы: Когда твоему рабочему нужно слушать сразу двух начальников (например, юзера и системный таймаут).
    
    var userCts = new CancellationTokenSource(); // Юзер может нажать крестик
    var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); // Система не будет ждать дольше 30 сек

// Скрепляем их бумажки в одну супер-бумажку using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource( userCts.Token, timeoutCts.Token );

await КачаюМемыБесконечноAsync(linkedCts.Token); // Рабочий слушает обоих // Достаточно одному начальнику орануть — и в супер-бумажке тоже будет "ПИЗДЕЦ"


*   **Колбэк на отмену:** Это типа когда рабочий, увидев «ПИЗДЕЦ» на бумажке, перед тем как свалить, должен всё же выключить свет и закрыть дверь.
```csharp
cancellationToken.Register(() =>
{
    Console.WriteLine("Ладно, отменяюсь, но сперва сохраню черновик...");
    // Тут срочная очистка, освобождение файлов и т.д.
});

Главное, что надо запомнить:

  • Отмена — добровольная. Если рабочий-метод упёртый мудак и не смотрит на бумажку (cancellationToken), то он будет работать, пока комп не выключится. Это кооперативная система, а не приказ расстрелять процесс.
  • Крикнув «ПИЗДЕЦ» один раз (Cancel()), начальник охрипнет навсегда. Токен нельзя использовать заново.
  • Всякие современные штуки в .NET (Task.Delay, HttpClient, EF Core) — они хорошие, воспитанные ребята. Если дать им эту бумажку, они сами будут её проверять и аккуратно прекращать работу.