Отмена задач может потребоваться по различным причинам — долгое выполнение задач, отмена выполнения каких-либо действий, в случае, если задача завершилась с ошибкой и так далее. В 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, используя которое, можно отменять задачи в которых поток может останавливаться на определенное время.