День сто восемьдесят четвёртый. #ЗаметкиНаПолях
Многопоточность.
4. Задания. Начало
Проблема с асинхронными вычислениями, запущенными с помощью метода
Отмена задания
Для отмены задания можно воспользоваться объектом
Продолжение следует…
Источник: Джеффри Рихтер “CLR via C#”. 3-е изд. – СПб.: Питер, 2012. Глава 26.
Многопоточность.
4. Задания. Начало
Проблема с асинхронными вычислениями, запущенными с помощью метода
ThreadPool.QueueUserWorkItem()
состоит в том, что отсутствует встроенный механизм, позволяющий узнать о завершении операции и получить возвращаемое значение. Для решения этой проблемы используются задания. Следующие вызовы асинхронных операций аналогичны:ThreadPool.QueueUserWorkItem(Compute, 5);Объекту
new Task(Compute, 5).Start();
Task
передаётся делегат Action
или Action<object>
(в последнем случае следует также передать аргумент метода, как в примере выше). При желании также можно передать структуру CancellationToken
для отмены задания. Допустим, метод Calc
принимает целое число и производит вычисления. Тогда запустить его в новом задании можно следующим образом:var t = new Task<int>((n) => Calc((int)n), 10000);Поток запускает задание и дожидается его выполнения при вызове метода
t.Start();
t.Wait();
Console.WriteLine($"Результат: " + t.Result);
Console.ReadLine();
Wait
или свойства Result
. При этом само задание может выполниться как в новом потоке (тогда поток, вызвавший метод Wait
, блокируется), либо в том же потоке.Отмена задания
Для отмены задания можно воспользоваться объектом
CancellationTokenSource
(см. https://t.me/NetDeveloperDiary/219 ). Однако, в отличие от обычной асинхронной операции, для отмены задания нужно обратиться не к свойству IsCancellationRequested
, а вызывать метод ThrowIfCancellationRequested()
токена отмены. Это приводит к исключению OperationCancelledException
, которое нужно перехватить в вызывающем коде. Причина в том, что задания возвращают результат. Поэтому, чтобы отличить законченное задание от незаконченного, используется исключение.private static int Calc(CancellationToken ct, int n)Все необработанные исключения, возникающие в задании, проглатываются в исполняющем потоке и сохраняются в коллекции
{
…
ct.ThrowIfCancellationRequested();
…
}
var cts = new CancellationTokenSource();
var t = new Task<int>(() => Calc(cts.Token, 10000), cts.Token);
t.Start();
// вызывается асинхронно
// к этому моменту задание может быть уже выполнено
cts.Cancel();
try
{
Console.WriteLine($"Результат: {t.Result}");
}
catch(AggregateException ex)
{
ex.Handle(x => x is OperationCanceledException);
Console.WriteLine("Операция отменена");
}
InnerExceptions
объекта AggregateException
. Поэтому в вызывающем коде перехватывается именно исключение типа AggregateException
. Далее с помощью метода Handle()
можно отметить нужные нам исключения, как обработанные. В данном случае мы считаем все исключения типа OperationCanceledException
обработанными. Если в коллекции после вызова метода Handle
останутся необработанные исключения, они попадут в новый объект AggregateException
.Продолжение следует…
Источник: Джеффри Рихтер “CLR via C#”. 3-е изд. – СПб.: Питер, 2012. Глава 26.