Отмена задач может потребоваться по различным причинам — долгое выполнение задач, отмена выполнения каких-либо действий, в случае, если задача завершилась с ошибкой и так далее. В TPL предусмотрена возможность отмены параллельных задач с использованием специального объекта — CancellationToken
.
Пример отмены параллельной задачи
В рассматриваемом примере, в параллельной задаче определяются простые числа. В случае, если пользователь вводит в консоль символ «N
«, выполнение задачи прерывается.
using System; using System.Threading; using System.Threading.Tasks; namespace TaskCancel { internal class Program { static CancellationTokenSource cancelTokenSource = new CancellationTokenSource(); static CancellationToken token = cancelTokenSource.Token; static void Main(string[] args) { Task<int> primeTask = new Task<int>(() => TaskMethod(), token); primeTask.Start(); Console.WriteLine("Введите N для отмены операции"); string s = Console.ReadLine(); if (s.ToUpper() == "N") cancelTokenSource.Cancel(); Console.WriteLine($"Обнаружено {primeTask.Result} простых чисел"); Console.Read(); } static int TaskMethod() { int count = 0; int i = 2; while (!token.IsCancellationRequested) { if (IsPrime(i)) { count++; Console.Write($"{i} "); } i++; Thread.Sleep(100); } Console.WriteLine($"Операция отменена."); return count; } public static bool IsPrime(int number) { for (int i = 2; i < number; i++) { if (number % i == 0) return false; } return true; } } }
Для того, чтобы получить возможность прерывать выполнение параллельной задачи, нам необходимо создать токен отмены. Для этого, мы вначале создаем объект:
static CancellationTokenSource cancelTokenSource = new CancellationTokenSource();
и, затем, получаем сам токен:
static CancellationToken token = cancelTokenSource.Token;
Полученный токен мы передаем в конструктор задачи:
Task<int> primeTask = new Task<int>(() => TaskMethod(), token);
Чтобы задача была отменена, мы, при выполнении какого-либо условия, вызываем метод Cancel()
объекта CancellationTokenSource
. После того, как вызван метод Cancel()
, свойство токена token.IsCancellationRequested
возвращает true
и в методе задачи происходит прерывание цикла while
.
Как только пользователь вводит с клавиатуры символ «n» и жмет Enter, задача прерывается и в консоль выводится количество найденных простых чисел.
Прерывание «спящей» задачи
В рассмотренном выше примере вызывается уже известный нам метод Thread.Sleep()
, который заставляет текущий поток уснуть на n
миллисекунд. Если в примере написать следующее:
Thread.Sleep(10000);
т.е. усыпить поток на 10 секунда и попробовать остановить задачу с помощью токена отмены, то произойдет следующее: поток дойдет до строки с Thread.Sleep(10000)
, уснет на 10 секунд и только после этого задача будет отменена. Чтобы получить возможность остановить задачу со «спящим» потоком, можно использовать следующий подход:
static int TaskMethod() { int count = 0; int i = 2; while (!token.IsCancellationRequested) { //проверяем числа var canceled = token.WaitHandle.WaitOne(10000); if (canceled) break; // Thread.Sleep(10000); } Console.WriteLine($"Операция отменена."); return count; }
здесь свойство WaitHandle
возвращает дескриптор WaitHandle
, получающий сигнал при отмене токена. Класс WaitHandle
инкапсулирует собственный обработчик синхронизации операционной системы и используется для представления всех объектов синхронизации в среде выполнения, которые допускают несколько операций ожидания. Метод WaitOne()
(про этот метод говорилось в теме про синхронизацию потоков) блокирует текущий поток до получения сигнала объектом WaitHandle
.
Теперь, если запустить пример и попробовать остановить задачу, то задача отменится сразу после того, как пользователь наберет «N» и нажмет Enter.
Итого
Для отмены параллельных задач используется специальный объект класса CancellationToken
, который необходимо передавать в конструкторе задаче или в качестве параметра внешнего метода задачи. Кроме того, что объект типа CancellationToken
может использоваться как обычный флаг (свойство IsCancellationRequested
) отмены задачи, в этом объекте также имеется свойство WaitHandle
, используя которое, можно отменять задачи в которых поток может останавливаться на определенное время.