Зачем вам нужен токен отмены в C# для задач?

Зачем вам нужен токен отмены в C# для задач?

30 марта 2022 г.

[CancellationToken] (https://docs.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken?view=net-6.0) позволяет совместную отмену между потоками, рабочими элементами пула потоков или [задачей]. (https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task?view=net-6.0) объектов. В этой статье я хотел бы обсудить механизм, применимый к объектам Task.


Когда вы запускаете задачу на C#, ее выполнение может занять некоторое время. В некоторых случаях вы хотели бы отменить такую ​​длительную операцию. Причин может быть несколько: тайм-аут операции, превышение лимита ресурсов и т. д.


Алгоритм


  1. Создайте объект типа CancellationTokenSource, который сигнализирует об отмене токену.

  1. Передайте свойству CancellationTokenSource.Token задачу в качестве объекта токена.

  1. Определить поведение задачи на завершение операции по сигналу отмены.

  1. Вызов метода CancellationTokenSource.Cancel(), который устанавливает для свойства CancellationToken.IsCancellationRequested значение true. Это означает, что метод Cancel() не отменяет саму операцию. Он просто изменяет значение свойства IsCancellationRequested. Мы, как разработчики, должны сами определить логику отмены.

Тип CancellationTokenSource реализует интерфейс IDisposable и должен быть освобожден после завершения задачи. Это можно сделать вручную, вызвав метод Dispose() или конструкцию vi using.


Пример кода для демонстрации приведенного выше алгоритма:


```csharp


// инициализируем объекты отмены


CancellationTokenSource cancelTokenSource = новый CancellationTokenSource();


Токен CancellationToken = cancelTokenSource.Token;


// выполняем параллельную операцию


Task task = new Task(() => {some_operations}, token);


задача.Начать();


// отменить операцию


CancelTokenSource.Cancel();


// освобождаем ресурсы


CancelTokenSource.Dispose();


Давайте подробно обсудим шаг №3. Есть два способа определить логику завершения задачи с помощью токена отмены:


  1. Используйте оператор return для выхода из выполнения задачи. В этом случае состояние задачи будет «TaskStatus.RunToCompletion».

  1. Сгенерировать исключение типа OperationCanceledException через вызов метода ThrowIfCancellationRequested(). В этом случае состояние задачи будет «TaskStatus.Canceled».

Завершить задачу с помощью оператора return


```csharp


public static void Main(string[] args)


CancellationTokenSource cancelTokenSource = новый CancellationTokenSource();


Токен CancellationToken = cancelTokenSource.Token;


Задача задача = новая задача (() =>


для (целое я = 1; я < 100; я ++)


если (токен.IsCancellationRequested)


Console.WriteLine("Операция отменена");


вернуть;


Console.WriteLine($"Счетчик равен '{i}'");


//добавляем тайм-аут для имитации реального выполнения


Нить.Сон(10);


}, токен);


задача.Начать();


// добавить тайм-аут для имитации реального выполнения


Нить.Сон(100);


// отменяем параллельную операцию


CancelTokenSource.Cancel();


// ждем завершения операции


задача.Подождите();


// проверяем статус операции


Console.WriteLine($"Статус задачи равен '{task.Status}'");


// освобождаем ресурсы


CancelTokenSource.Dispose();


Результат этого выполнения следующий:


``` ударить


Количество равно '1'


Количество равно '2'


Количество равно '3'


Количество равно '4'


Количество равно '5'


Операция отменена


Статус задачи равен «RanToCompletion».


Завершить задачу с помощью вызова метода ThrowIfCancellationRequested()


```csharp


public static void Main(string[] args)


CancellationTokenSource cancelTokenSource = новый CancellationTokenSource();


Токен CancellationToken = cancelTokenSource.Token;


Задача задача = новая задача (() =>


для (целое я = 1; я < 100; я ++)


если (токен.IsCancellationRequested)


token.ThrowIfCancellationRequested();


Console.WriteLine($"Счетчик равен '{i}'");


//добавляем тайм-аут для имитации реального выполнения


Нить.Сон(10);


}, токен);


пытаться


задача.Начать();


// добавить тайм-аут для имитации реального выполнения


Нить.Сон(100);


// отменяем параллельную операцию


CancelTokenSource.Cancel();


// ждем завершения операции


задача.Подождите();


поймать (AggregateException ae)


foreach (Исключение e в ae.InnerExceptions)


если (e есть TaskCanceledException)


Console.WriteLine("Операция отменена");


еще


Console.WriteLine(e.Message);


наконец


// освобождаем ресурсы


CancelTokenSource.Dispose();


// проверяем статус операции


Console.WriteLine($"Статус задачи равен '{task.Status}'");


Результат этого выполнения следующий:


``` ударить


Количество равно '1'


Количество равно '2'


Количество равно '3'


Количество равно '4'


Количество равно '5'


Операция отменена


Статус задачи равен «Отменено».


Выброшенное исключение будет отображаться как InnerException из AggregateException. Если задача была отменена с помощью вызова метода ThrowIfCancellationRequested(), исключение будет иметь тип TaskCanceledException. Код проверяет правильность обработки этого типа, в противном случае обрабатывает другую причину исключения.


Исключение будет выброшено только в том случае, если для задачи вызывается метод Wait() или WaitAll(). В противном случае исключение не генерируется, просто устанавливается TaskStatus.Canceled.


Зарегистрировать обработчик отмены операции


Другой способ определить логику отмены задачи — использовать Register() метод. Он регистрирует делегат Action, который будет вызываться, когда CancellationToken отменяется.


```csharp


public static void Main(string[] args)


CancellationTokenSource cancelTokenSource = новый CancellationTokenSource();


Токен CancellationToken = cancelTokenSource.Token;


Задача задача = новая задача (() =>


интервал я = 1;


токен.Регистрация(() =>


Console.WriteLine("Операция отменена");


я = 100;


Console.WriteLine($"Счетчик равен '{i}'");


для (; я < 100; я ++)


Console.WriteLine($"Счетчик равен '{i}'");


//добавляем тайм-аут для имитации реального выполнения


Нить.Сон(10);


}, токен);


задача.Начать();


// добавить тайм-аут для имитации реального выполнения


Нить.Сон(100);


// отменяем параллельную операцию


CancelTokenSource.Cancel();


// ждем завершения операции


задача.Подождите();


// проверяем статус операции


Console.WriteLine($"Статус задачи равен '{task.Status}'");


// освобождаем ресурсы


CancelTokenSource.Dispose();


Результат этого выполнения следующий:


``` ударить


Количество равно '1'


Количество равно '2'


Количество равно '3'


Количество равно '4'


Количество равно '5'


Операция отменена


Количество равно '100'


Статус задачи равен «RanToCompletion».


В этом коде вызывается метод cancelTokenSource.Cancel(), и срабатывает делегат, определенный в методе token.Register(). В этом примере код устанавливает переменную i в значение 100, что приводит к завершению выполнения задачи.


Если код не ожидает выполнения операции, статус задачи будет «TaskStatus.Running». Если вызывается метод Wait() или WaitAll(), статус задачи будет TaskStatus.RanToCompletion.


Резюме: использование токена отмены


Отмена задачи очень важна для оптимизации логики вашего приложения. Вам может понадобиться отменить задачу по многим причинам: тайм-аут операции, превышение лимита ресурсов и т. д. Вам всегда нужно самостоятельно обрабатывать логику отмены. Вы можете сделать это через оператор return или через вызов метода ThrowIfCancellationRequested().



Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE