Выполнение задач параллельно
Хорошо, так что в основном у меня есть множество задач (10), и я хочу начать их все в одно и то же время и дождаться их завершения. По завершении я хочу выполнить другие задачи. Я прочитал кучу ресурсов об этом, но я не могу понять это в своем конкретном случае …
Вот что я сейчас (код был упрощен):
public async Task RunTasks() { var tasks = new List { new Task(async () => await DoWork()), //and so on with the other 9 similar tasks } Parallel.ForEach(tasks, task => { task.Start(); }); Task.WhenAll(tasks).ContinueWith(done => { //Run the other tasks }); } //This function perform some I/O operations public async Task DoWork() { var results = await GetDataFromDatabaseAsync(); foreach (var result in results) { await ReadFromNetwork(result.Url); } }
Поэтому моя проблема заключается в том, что, когда я жду, когда задачи будут завершены WhenAll
, он сообщает мне, что все задачи завершены, хотя ни одна из них не завершена. Я попытался добавить Console.WriteLine
в свой foreach
и когда я вошел в задачу продолжения, данные продолжают поступать из моей предыдущей Task
, которые на самом деле не закончены.
- Каков правильный способ отмены асинхронной операции, которая не принимает CancellationToken?
- Запуск задач в foreach Loop использует значение последнего элемента
- Разница между TPL и async / await (Обработка streamов)
- Использовать Task.Run () в синхронном методе, чтобы избежать тупиковой остановки в асинхронном методе?
- WaitAll vs WhenAll
Что я здесь делаю неправильно?
- Запуск двух асинхронных задач параллельно и сбор результатов в .NET 4.5
- Отмена ожидающей задачи синхронно в streamе пользовательского интерфейса
- Как ограничить количество одновременных операций асинхронного ввода-вывода?
- Запуск нескольких задач async и ожидание их завершения
- Лучший способ конвертировать метод async на основе обратного вызова в ожидаемую задачу
- Почему это асинхронное действие зависает?
- Секвенирование и реорганизация задач
- Когда использовать Task.Delay, когда использовать Thread.Sleep?
Вы почти никогда не должны использовать конструктор Task
напрямую. В вашем случае эта задача вызывает только ту задачу, которую вы не можете ждать.
Вы можете просто вызвать DoWork
и вернуть задачу, сохранить ее в списке и дождаться завершения всех задач. Имея в виду:
tasks.Add(DoWork()); // ... await Task.WhenAll(tasks);
Тем не менее, асинхронные методы выполняются синхронно до тех пор, пока не будет достигнуто первое ожидание по незавершенной задаче. Если вы беспокоитесь о том, что эта часть занимает слишком много времени, используйте Task.Run
чтобы разгрузить ее в другой stream ThreadPool
а затем сохраните эту задачу в списке:
tasks.Add(Task.Run(() => DoWork())); // ... await Task.WhenAll(tasks);
По сути, вы смешиваете две несовместимые асинхронные парадигмы; т.е. Parallel.ForEach()
и async-await
.
Для чего вы хотите, сделайте то или другое. Например, вы можете просто использовать Parallel.For[Each]()
и вообще отказаться от async-ожидания. Parallel.For[Each]()
будет возвращаться только после завершения всех параллельных задач, а затем вы можете перейти к другим задачам.
В коде есть и другие проблемы:
-
вы отмечаете метод async, но не ожидаете в нем (ожидание, которое у вас есть, есть в делетете, а не в методе);
-
вы почти наверняка хотите
.ConfigureAwait(false)
на вашем.ConfigureAwait(false)
, особенно если вы не пытаетесь использовать результаты сразу в streamе пользовательского интерфейса.
Если вы хотите запустить параллельную параллельность этих задач в разных streamах с помощью TPL, вам может понадобиться что-то вроде этого:
public async Task RunTasks() { var tasks = new List> { DoWork, //... }; await Task.WhenAll(tasks.AsParallel().Select(async task => await task())); //Run the other tasks }
Этот подход распараллеливает только небольшой объем кода: очередь процесса в пул streamов и возврат незавершенной Task
. Кроме того, для такого небольшого количества задач распараллеливание может занимать больше времени, чем просто работать асинхронно. Это может иметь смысл только в том случае, если ваши задачи выполняют более длительную (синхронную) работу до их первого ожидания.
В большинстве случаев лучшим способом будет:
public async Task RunTasks() { await Task.WhenAll(new [] { DoWork(), //... }); //Run the other tasks }
На мой взгляд, в вашем коде:
-
Вы не должны обертывать свой код в
Task
перед переходом наParallel.ForEach
. -
Вы можете просто
await
Task.WhenAll
вместо использованияContinueWith
.
Метод DoWork
является асинхронным методом ввода-вывода. Это означает, что вам не нужно несколько streamов для выполнения нескольких из них, так как большую часть времени метод будет асинхронно ждать завершения ввода-вывода. Одного streamа достаточно, чтобы сделать это.
public async Task RunTasks() { var tasks = new List { DoWork(), //and so on with the other 9 similar tasks }; await Task.WhenAll(tasks); //Run the other tasks }
Вы почти никогда не должны использовать конструктор Task
для создания новой задачи. Чтобы создать асинхронную задачу ввода-вывода, просто вызовите метод async
. Чтобы создать задачу, которая будет выполнена в streamе пула streamов, используйте Task.Run
. Вы можете прочитать эту статью для подробного объяснения Task.Run
и других вариантов создания задач.
Просто добавьте блок try-catch вокруг Task.WhenAll
NB: генерируется экземпляр System.AggregateException, который действует как shell вокруг одного или нескольких исключений, которые произошли. Это важно для методов, которые координируют несколько задач, таких как Task.WaitAll () и Task.WaitAny (), поэтому AggregateException может обернуть все исключения в выполняемых задачах, которые произошли.
try { Task.WaitAll(tasks.ToArray()); } catch(AggregateException ex) { foreach (Exception inner in ex.InnerExceptions) { Console.WriteLine(String.Format("Exception type {0} from {1}", inner.GetType(), inner.Source)); } }