Как ожидать список задач асинхронно с помощью LINQ?
У меня есть список задач, которые я создал следующим образом:
public async Task<IList<Foo>> GetFoosAndDoSomethingAsync()
{
var foos = await GetFoosAsync();
var tasks = foos.Select(async foo => await DoSomethingAsync(foo)).ToList();
...
}
Используя .ToList()
, все задачи должны начинаться. Теперь я хочу дождаться их завершения и вернуть результаты.
Это работает в предыдущем блоке ...
:
var list = new List<Foo>();
foreach (var task in tasks)
list.Add(await task);
return list;
Он делает то, что я хочу, но это кажется довольно неуклюжим. Я бы скорее написал что-то более простое:
return tasks.Select(async task => await task).ToList();
... но это не скомпилируется. Что мне не хватает? Или это просто невозможно выразить так?
Ответы
Ответ 1
LINQ не работает отлично с кодом async
, но вы можете сделать это:
var tasks = foos.Select(DoSomethingAsync).ToList();
await Task.WhenAll(tasks);
Если ваши задачи возвращают одинаковый тип значения, вы можете даже сделать это:
var results = await Task.WhenAll(tasks);
что довольно приятно. WhenAll
возвращает массив, поэтому я считаю, что ваш метод может напрямую возвращать результаты:
return await Task.WhenAll(tasks);
Ответ 2
Чтобы расширить ответ Стивена, я создал следующий метод расширения, чтобы сохранить свободный стиль LINQ. Затем вы можете сделать
await someTasks.WhenAll()
namespace System.Linq
{
public static class IEnumerableExtensions
{
public static Task<T[]> WhenAll<T>(this IEnumerable<Task<T>> source)
{
return Task.WhenAll(source);
}
}
}
Ответ 3
Используйте Task.WaitAll
или Task.WhenAll
в зависимости от того, какой из них является approriate.
Ответ 4
Task.WhenAll должен сделать трюк здесь.
Ответ 5
Одна проблема с Task.WhenAll заключается в том, что она создаст parallelism. В большинстве случаев это может быть даже лучше, но иногда вы хотите избежать этого. Например, чтение данных в пакетах из БД и отправка данных на некоторые удаленные веб-службы. Вы не хотите загружать все партии в память, но попадаете в БД после обработки предыдущей партии. Итак, вы должны нарушить асинхронность. Вот пример:
var events = Enumerable.Range(0, totalCount/ batchSize)
.Select(x => x*batchSize)
.Select(x => dbRepository.GetEventsBatch(x, batchSize).GetAwaiter().GetResult())
.SelectMany(x => x);
foreach (var carEvent in events)
{
}
Примечание .GetAwaiter(). GetResult() преобразует его в синхронизацию. БД удавалось лениво только после обработки пакета событий.