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

Отмена асинхронных операций

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

1. Введение

Когда вы запускаете асинхронную (или длительную) операцию, пользователь (или другой код) может вдруг решить: "Стой! Уже не нужно! Остановись!". Например, пользователь решил прервать загрузку огромного файла, закрыл окно программы или передумал искать информацию в большой базе данных. Без поддержки отмены ваша программа может продолжать работать и поедать ресурсы впустую — не лучший показатель заботы о пользователе и компьютере.

Типичные сценарии отмены:

  • Отмена загрузки файла или отмена отправки данных.
  • Быстрый выход из сложной обработки данных по требованию пользователя.
  • При резкой паузе/остановке длительной задачи, которая больше не актуальна.

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

Как отменять асинхронные операции в .NET?

В .NET для отмены длительных задач используется концепция "токена отмены" (CancellationToken). Это специальный объект, который передаётся во все компоненты задачи. Если кто-то попросил отменить операцию — токен мгновенно сообщает об этом всем заинтересованным частям программы. На деле это похоже на красный флажок: кто первым увидит — тот и остановится.

В .NET этот механизм реализован с помощью двух ключевых классов:

  • CancellationTokenSource — создаёт токен отмены и управляет им.
  • CancellationToken — передаётся в асинхронные методы, чтобы их можно было отменить.

Важно: сам по себе токен отмены не прерывает выполнение кода, он только "просигнализирует" — а уж ваше приложение само решает, когда и как реагировать на этот сигнал.

2. Создаём токен отмены и отменяем задачу

Давайте посмотрим, как это работает, на простом примере (будем развивать наше обучающее консольное приложение).

Пример: простая асинхронная операция с отменой


using System;
using System.Threading;
using System.Threading.Tasks;

namespace DemoApp
{
    class Program
    {
        static async Task Main()
        {
            // Создаем источник токена отмены
            CancellationTokenSource cts = new CancellationTokenSource();

            // Запускаем асинхронную задачу
            Task longRunningTask = DoWorkAsync(cts.Token);

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

            // Запрашиваем отмену
            cts.Cancel();

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

        // Асинхронный метод, поддерживающий отмену
        static async Task DoWorkAsync(CancellationToken cancellationToken)
        {
            for (int i = 0; i < 10; i++)
            {
                // Проверяем сигнал отмены
                cancellationToken.ThrowIfCancellationRequested();

                Console.WriteLine($"Выполнение шага {i + 1}/10...");
                await Task.Delay(1000); // Задержка 1 секунда
            }

            Console.WriteLine("Операция завершена успешно!");
        }
    }
}

Как это работает?

- Мы создаём CancellationTokenSource (cts), откуда получаем токен (cts.Token).
- Передаём этот токен в нашу асинхронную операцию.
- Внутри DoWorkAsync() регулярно проверяем токен на отмену методом ThrowIfCancellationRequested(). Если пользователь попросит отменить задачу — метод бросит исключение OperationCanceledException, и задача прервётся.
- В Main() ждём нажатия любой клавиши и вызываем cts.Cancel(), чтобы "сигнализировать" о необходимости остановки операции.

Если вы не будете проверять cancellationToken.IsCancellationRequested или не вызовете ThrowIfCancellationRequested(), ваша задача продолжит работать, как ни в чём не бывало — токен лишь информационный флажок.

3. CancellationToken: как это устроено? И немного магии

Токен отмены — это объект, который легко можно передать между методами и задачами. Это даёт большую гибкость:

  • Один и тот же токен можно использовать в нескольких асинхронных и синхронных операциях.
  • Можно организовать "групповую" отмену сразу для всех задач, если несколько операций используют токен из одного CancellationTokenSource.
  • Токен отмены ненавязчив: даже если вы его игнорируете, код продолжит работать как прежде.

Управление отменой: где и как проверять токен?

Проверять, был ли поднят "флажок отмены", можно и нужно в тех местах, где это логически оправдано: в цикле, на каждом шаге долгой обработки, при переходе между этапами и т.д.


// В любой момент проверки
if (cancellationToken.IsCancellationRequested)
{
    Console.WriteLine("Операция отменена! Выходим...");
    return;
}

// Или так (коротко и с выбросом исключения)
cancellationToken.ThrowIfCancellationRequested();

Обычно используют ThrowIfCancellationRequested() — он бросает специальное исключение, которое можно отловить в вызывающем коде.

4. Асинхронные методы стандартной библиотеки

Очень много классов и методов .NET (особенно асинхронных) поддерживают CancellationToken прямо "из коробки". Их можно и нужно использовать, чтобы "корректно" останавливать операции.

Вот пример с асинхронным чтением файла:


using System.IO;
using System.Threading;
using System.Threading.Tasks;

class FileDemo
{
    public static async Task ReadFileWithCancelAsync(string filePath, CancellationToken cancellationToken)
    {
        using FileStream stream = File.OpenRead(filePath);
        byte[] buffer = new byte[4096];

        int bytesRead;
        while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken)) > 0)
        {
            // Обрабатываем данные...
            // Если токен отмены был запрошен, ReadAsync сам выбросит OperationCanceledException
        }
    }
}

Подробнее про FileStream.ReadAsync и CancellationToken читайте в официальной документации.

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

Таймаут — это тоже отмена!

Вы можете автоматически отменять операции по истечении заданного времени. Для этого токен отмены можно "запрограммировать":


// Создать CancellationTokenSource c таймаутом (например, 5 секунд)
CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));

Через 5 секунд токен будет автоматически "поднят", и все операции с ним остановятся при следующей проверке. Это очень удобно, если вы не хотите ждать вечно.

Что происходит при отмене?

Когда вы вызываете Cancel() на источнике токена, все методы, которые используют этот токен и проверяют его состояние, узнают об этом изменении. Но если ваш код не проверяет токен — отмена не произойдёт.

Типичная ошибка: забыть передать токен во все асинхронные и долгие операции. Тогда часть операции будет отменена, а часть — продолжит работать, как ничего не случилось.

Визуализация: как протекает отмена

sequenceDiagram
    participant Main as Главный поток
    participant CTS as CancellationTokenSource
    participant Task as Асинхронная задача

    Main->>CTS: создаёт CTS, получает токен
    Main->>Task: передаёт токен в асинхронную задачу
    Note over Task: Задача периодически проверяет токен
    Main->>CTS: вызывает Cancel()
    CTS-->>Task: токен получает статус "отменено"
    Task-->>Main: выбрасывает OperationCanceledException
    Main->>Main: ловит исключение и завершает работу

Где применяется отмена асинхронных операций?

  • Асинхронные загрузки и запросы к серверу: можно отменить при плохом интернете или утомлении пользователя.
  • Большие вычисления: можно остановить по таймауту или по желанию пользователя.
  • Сетевые операции, работа с файлами, обработка больших коллекций в фоновом режиме.

На этом знакомство с отменой асинхронных операций завершено — теперь ваше приложение станет не только быстрым, но и заботливым!

6. Советы и типичные ошибки

Не забывайте передавать токен отмены во все методы и вызовы, поддерживающие cancellation. Если хоть где-то не пробросите токен — операция может "зависнуть" и не остановиться.

Проверяйте токен регулярно — особенно в долгих циклах, обработках файлов или загрузках больших объёмов. Используйте IsCancellationRequested или ThrowIfCancellationRequested().

Не пытайтесь "принудительно" завершить поток или задачу снаружи: токен отмены — это просьба остановиться, а не дубинка по голове потока.

Функции стандартной библиотеки, такие как ReadAsync, Delay, HttpClient.SendAsync и многие другие, уже поддерживают отмену через токен. Используйте это!

При обработке отмены ловите именно OperationCanceledException — это специальное исключение, информирующее о корректной отмене по запросу.

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