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

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

5. Задания. Продолжение

Автоматический запуск задания по завершении предыдущего

Вызов метода Wait или свойства Result при незавершённом задании скорее всего приведёт к блокировке текущего потока и появлению в пуле нового потока. Чтобы этого не происходило, можно переписать код из предыдущих примеров так, чтобы результат выводился в новом задании, которое стартует после завершения предыдущего:

var t = new Task<int>((n) => Calc((int)n), 10000);

t.Start();

Task cwt = t.ContinueWith(task => Console.WriteLine($"Результат: {task.Result}"));

При этом поток, исполняющий этот код и ожидающий завершения каждого из этих заданий, не блокируется, а выполняет другую работу или возвращается в пул. Поскольку метод ContinueWith также возвращает Task, то и к нему, в свою очередь, можно применять Wait, Result или ContinueWith.

Кроме того, вызвать ContinueWith можно с флагами TaskContinuationOptions. Наиболее интересные из них:

- AttachedToParent – присоединяет задание к родительскому (родительское задание не закончится, пока не исполнятся все дочерние)

- ExecuteSynchronously – новое задание будет выполнено тем же потоком, что и предыдущее

- OnlyOnCanceled – выполнять, если предыдущее задание отменено

- OnlyOnFaulted – выполнять, если предыдущее задание привело к ошибке

- OnlyOnRanToCompletion – выполнять, если предыдущее задание завершилось успешно

- NotOnCanceled, NotOnFaulted, NotOnRanToCompletion – соответствующие отрицательные флаги.

Вывод результата в предыдущем примере можно переписать с этими флагами:

t.ContinueWith(task => 

Console.WriteLine($"Результат: {task.Result}"),

TaskContinuationOptions.OnlyOnRanToCompletion);

t.ContinueWith(task =>

Console.WriteLine($"Ошибка: {task.Exception}"),

TaskContinuationOptions.OnlyOnFaulted);

t.ContinueWith(task =>

Console.WriteLine("Операция отменена"),

TaskContinuationOptions.OnlyOnCanceled);



Фабрики заданий

Если необходим набор заданий в одном состоянии и с одинаковыми параметрами, можно создать фабрику заданий TaskFactory:

string[] files = null

string[] dirs = null;

string docsDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);



var tf = new TaskFactory();

var tasks = new[]

{

tf.StartNew(() => files = Directory.GetFiles(docsDir)),

tf.StartNew(() => dirs = Directory.GetDirectories(docsDir))

};

tf.ContinueWhenAll(tasks, completed => {

Console.WriteLine("{0} содержит: ", docsDir);

Console.WriteLine(" {0} папок", dirs.Length);

Console.WriteLine(" {0} файлов", files.Length);

});

В коде выше создаётся новая фабрика заданий. Ей можно передать общий токен отмены, параметры создания и продолжения заданий и т.п. Далее задания создаются вызовом метода StartNew. Впоследствии можно добавить задание-продолжение по окончании всех или любого из заданий, вызвав соответственно ContinueWhenAll или ContinueWhenAny. В примере выше в фабрике заданий два параллельных задания подсчитывают количество папок и файлов в папке «Мои документы» текущего пользователя, а после завершения всех заданий результаты выводятся в консоль.



Продолжение следует…



Источники:

- Джеффри Рихтер “CLR via C#”. 3-е изд. – СПб.: Питер, 2012. Глава 26.

-
https://docs.microsoft.com/dotnet/api/system.threading.tasks.task.factory