Зачем вам нужен токен отмены в 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#, ее выполнение может занять некоторое время. В некоторых случаях вы хотели бы отменить такую длительную операцию. Причин может быть несколько: тайм-аут операции, превышение лимита ресурсов и т. д.
Алгоритм
- Создайте объект типа CancellationTokenSource, который сигнализирует об отмене токену.
- Передайте свойству
CancellationTokenSource.Token
задачу в качестве объекта токена.
- Определить поведение задачи на завершение операции по сигналу отмены.
- Вызов метода
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. Есть два способа определить логику завершения задачи с помощью токена отмены:
- Используйте оператор return для выхода из выполнения задачи. В этом случае состояние задачи будет «TaskStatus.RunToCompletion».
- Сгенерировать исключение типа 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()
.
Оригинал