Как асинхронно ожидать список задач с помощью LINQ?


87

У меня есть список задач, которые я создал вот так:

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();

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


Вам нужно DoSomethingAsync(foo)последовательно обрабатывать каждый foo, или это кандидат на Parallel.ForEach <Foo> ?
mdisibio

1
@mdisibio - Parallel.ForEachэто блокировка. Шаблон здесь взят из видео Джона Скита об асинхронном C # на Pluralsight . Он выполняется параллельно без блокировки.
Мэтт Джонсон-Пинт

@mdisibio - Нет. Они работают параллельно. Попробуй . (Вдобавок, похоже, мне не нужно, .ToList()если я просто собираюсь использовать WhenAll.)
Мэтт Джонсон-Пинт

Дело принято. В зависимости от того, как DoSomethingAsyncнаписано, список может выполняться или не выполняться параллельно. Я смог написать тестовый метод, который был, и версию, которой не было, но в любом случае поведение продиктовано самим методом, а не делегатом, создавшим задачу. Извините за путаницу. Однако, если DoSomethingAsycвозвращается Task<Foo>, то awaitin the delegate не является абсолютно необходимым ... Я думаю, что это было основным моментом, который я собирался попытаться высказать.
mdisibio

Ответы:


136

LINQ плохо работает с asyncкодом, но вы можете сделать это:

var tasks = foos.Select(DoSomethingAsync).ToList();
await Task.WhenAll(tasks);

Если все ваши задачи возвращают один и тот же тип значения, вы даже можете сделать это:

var results = await Task.WhenAll(tasks);

что довольно приятно. WhenAllвозвращает массив, поэтому я считаю, что ваш метод может возвращать результаты напрямую:

return await Task.WhenAll(tasks);

11
Просто хотел отметить, что это также может работать сvar tasks = foos.Select(foo => DoSomethingAsync(foo)).ToList();
mdisibio

1
или дажеvar tasks = foos.Select(DoSomethingAsync).ToList();
Тодд Менье

3
в чем причина того, что Linq не работает идеально с асинхронным кодом?
Эхсан Саджад

2
@EhsanSajjad: потому что LINQ to Objects синхронно работает с объектами в памяти. Некоторые ограниченные вещи работают, например Select. Но большинству не нравится Where.
Стивен Клири

4
@EhsanSajjad: если операция основана на вводе-выводе, то вы можете использовать asyncдля сокращения потоков; если он привязан к процессору и уже находится в фоновом потоке, то asyncне принесет никакой пользы.
Стивен Клири

9

Чтобы расширить ответ Стивена, я создал следующий метод расширения, чтобы сохранить свободный стиль 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);
        }
    }
}

10
Лично я бы назвал ваш метод расширенияToArrayAsync
Торвин

4

Одна проблема с Task.WhenAll заключается в том, что он создает параллелизм. В большинстве случаев это может быть даже лучше, но иногда вы хотите этого избежать. Например, пакетное чтение данных из БД и отправка данных в какой-то удаленный веб-сервис. Вы не хотите загружать все пакеты в память, а попадете в БД после обработки предыдущего пакета. Итак, вы должны нарушить асинхронность. Вот пример:

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



Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.