День сто восемьдесят четвёртый. #ЗаметкиНаПолях

Многопоточность.

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.