JavaRush /Курсы /C# SELF /Отмена операций: Cancellat...

Отмена операций: CancellationToken

C# SELF
60 уровень , 4 лекция
Открыта

1. Знакомство с CancellationToken

Представьте себе ситуацию: вы запускаете длительную загрузку файла, а затем вдруг вспоминаете, что Интернет у вас по тарифу и гигабайты на вес золота. Или пользователь запутался, запустил расчёт, а потом решил, что он ему не нужен. Он хочет нажать «Отмена» и не ждать завершения. Современные приложения должны быть отзывчивыми, а для этого мы должны уметь передавать сигнал об остановке в любой момент и корректно прерывать выполнение асинхронной операции.

Вот для этого в .NET и существует целая инфраструктура отмены — CancellationToken.

CancellationToken (буквально «токен отмены») — это специальный объект, который вы передаёте внутрь своей длительной операции. В любой момент вы можете сигнализировать об отмене через CancellationTokenSource, а операция должна периодически проверять токен (например, IsCancellationRequested) и корректно завершаться, при необходимости вызывая ThrowIfCancellationRequested().

Как он устроен? (Кратко)

  • Есть объект CancellationTokenSource, который «генерирует» токены и умеет их отменять (метод Cancel()).
  • Сам CancellationToken — это «сигнальный флажок», который можно раздавать многим операциям (через свойство Token у источника).
  • Операция периодически проверяет токен: если отмена запрошена, выходит из работы (или бросает OperationCanceledException).

Аналогия: вы начальник (вы — это CancellationTokenSource). Вы раздаёте своим подчинённым «бейджики» (это CancellationToken). Когда вы решаете, что пора всё отменять, вы поднимаете красный флаг — и все, у кого есть бейджик, тут же ретируются, оставляя недоеденный обед.

2. Как использовать CancellationToken

Создать источник токена отмены (CancellationTokenSource)

var cts = new CancellationTokenSource();

Получить сам токен (CancellationToken)

CancellationToken token = cts.Token;

Передать токен в асинхронный метод

Большинство стандартных асинхронных методов .NET принимают параметр типа CancellationToken. Например, HttpClient.GetAsync, Stream.ReadAsync, Task.Delay и др.

Пример — задержка с отменой:

await Task.Delay(10000, token); // Ждать 10 секунд — но можно отменить!

Запросить отмену (например, по кнопке или по таймеру)

cts.Cancel(); // Все операции, которые получили этот токен, узнают об отмене 

Проверять токен внутри метода

Внутри своих методов (особенно если работа долгая и циклическая) нужно регулярно проверять флаг отмены и выбрасывать исключение OperationCanceledException, если отмена запрошена:

token.ThrowIfCancellationRequested();

Или просто проверять свойство:

if (token.IsCancellationRequested)
{
    // Освобождаем ресурсы, выходим из метода
}

3. Пример: Добавим отмену в наше учебное приложение

Допустим, у нас есть приложение, которое скачивает данные с сайта. Давайте добавим туда возможность отменять скачивание, если пользователь передумал.

Базовый пример асинхронной загрузки

using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

class Downloader
{
    public async Task DownloadAsync(string url)
    {
        var client = new HttpClient();
        string content = await client.GetStringAsync(url); // Без отмены
        Console.WriteLine("Загрузка завершена!");
    }
}

Добавляем CancellationToken

public async Task DownloadAsync(string url, CancellationToken token)
{
    var client = new HttpClient();
    string content = await client.GetStringAsync(url, token); // Теперь с поддержкой отмены!
    Console.WriteLine("Загрузка завершена!");
}

Управляем отменой со стороны вызывающего кода

static async Task Main(string[] args)
{
    var downloader = new Downloader();
    var cts = new CancellationTokenSource();

    Console.WriteLine("Введите URL для загрузки:");
    string url = Console.ReadLine();

    var downloadTask = downloader.DownloadAsync(url, cts.Token);

    Console.WriteLine("Нажмите любую клавишу для отмены загрузки...");
    Console.ReadKey();

    cts.Cancel(); // Сигнал отмены

    try
    {
        await downloadTask;
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine("Загрузка отменена пользователем!");
    }
}

Вот так просто! Теперь пользователь может прервать операцию в любой момент.

4. Взаимодействие CancellationTokenSource и методов

flowchart TD
    A["Код пользователя (Main)"] -- создает --> B["CancellationTokenSource"]
    B -- выдает --> C["CancellationToken"]
    C -- передается --> D["Асинхронная операция"]
    A -- вызывает Cancel() --> B
    D -- периодически проверяет --> C
    C -- сообщает об отмене --> D
    D -- выбрасывает Exception или завершает работу --> A

5. Обработка отмены

Когда операция получает токен отмены, есть два варианта поведения:

Методы .NET сами выбросят исключение.
Если вы вызываете стандартные методы, типа Stream.ReadAsync, HttpClient.GetAsync или Task.Delay, и передаёте им токен, — как только будет вызвано Cancel(), эти методы сами бросят OperationCanceledException. Вам остаётся только перехватить это исключение.

Пользовательский асинхронный код.
Если вы самостоятельно реализуете «долгую» операцию, например, циклическую обработку или сложные вычисления, ваша ответственность — регулярно проверять token.IsCancellationRequested (или вызывать token.ThrowIfCancellationRequested()), чтобы реагировать на отмену корректно.

Пример: «Долгая» операция с ручной проверкой отмены

public async Task CalculatePrimesAsync(int max, CancellationToken token)
{
    for (int i = 2; i < max; i++)
    {
        token.ThrowIfCancellationRequested(); // Проверяем отмену

        if (IsPrime(i))
        {
            Console.WriteLine($"Простое число: {i}");
            await Task.Delay(100, token); // Даем "отдохнуть" (можно отменить)
        }
    }
    Console.WriteLine("Расчёт завершён!");
}

private bool IsPrime(int n)
{
    for (int i = 2; i <= Math.Sqrt(n); i++)
        if (n % i == 0) return false;
    return true;
}

6. Полезные нюансы

Методы и классы, поддерживающие CancellationToken

Класс/метод Поддерживает CancellationToken? Пример использования
Task.Delay
await Task.Delay(1000, token);
HttpClient.GetAsync
await client.GetAsync(url, token);
Stream.ReadAsync
await stream.ReadAsync(buffer, 0, len, token);
Task.Run
Task.Run(() => ..., token);
File.ReadAllTextAsync
await File.ReadAllTextAsync(path, token);
Thread.Sleep
Не поддерживает; лучше использовать Task.Delay
Ваши методы ✔ (если добавите поддержку!)
MyLongMethod(token)

Жизненный цикл отменяемой операции

sequenceDiagram
    participant Пользователь
    participant Main
    participant CancellationTokenSource
    participant Асинхронная операция

    Пользователь->>Main: Запуск операции
    Main->>CancellationTokenSource: Создание CTS
    Main->>Асинхронная операция: Запуск и передача CancellationToken
    Пользователь->>Main: Нажимает "Отмена"
    Main->>CancellationTokenSource: Вызывает Cancel()
    Асинхронная операция->>Асинхронная операция: Замечает отмену (\nIsCancellationRequested)
    Асинхронная операция-->>Main: Бросает OperationCanceledException
    Main->>Пользователь: Показывает сообщение "Операция отменена"

Где это используется в реальной жизни

  • UI-приложения: Прервать долгие загрузки, вычисления, работу с файлами, если пользователь захотел закрыть окно или отменить действие.
  • Серверные приложения: Если клиент оборвал соединение — лучше сразу отменить обработку запроса, чтобы не тратить ресурсы.
  • Обработка больших данных: Задачи могут быть очень длинными — нужно всегда давать возможность остановить расчёты или миграции.
  • Интеграция с оборудованием: Сканирование, печать и другие операции иногда нужно экстренно прервать — поддержка отмены тут обязательна.

7. Типичные ошибки при работе с CancellationToken

Ошибка №1: Игнорирование проверки токена.
Если операция не проверяет token.IsCancellationRequested или не вызывает ThrowIfCancellationRequested(), она не остановится при отмене, тратя ресурсы.

Ошибка №2: Неправильная обработка OperationCanceledException.
Если не перехватить OperationCanceledException, приложение может завершиться неожиданно. Всегда используйте try-catch для обработки отмены.

Ошибка №3: Неправильное управление ресурсами при отмене.
Отмена не откатывает изменения автоматически (например, в файлах или базах данных). Нужно вручную очищать ресурсы в блоке catch.

Ошибка №4: Передача уже отменённого токена.
Если токен уже отменён, метод сразу выбросит исключение, что может нарушить логику, если это не учтено.

2
Задача
C# SELF, 60 уровень, 4 лекция
Недоступна
Поиск простых чисел с поддержкой отмены
Поиск простых чисел с поддержкой отмены
1
Опрос
Сравнение Task и Thread, 60 уровень, 4 лекция
Недоступен
Сравнение Task и Thread
Параллельная обработка данных
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ