Подтвердить что ты не робот

Как ожидать список задач асинхронно с помощью 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();

... но это не скомпилируется. Что мне не хватает? Или это просто невозможно выразить так?

4b9b3361

Ответ 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() преобразует его в синхронизацию. БД удавалось лениво только после обработки пакета событий.