У меня есть список задач, которые я создал вот так:
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();
... но это не компилируется. Что мне не хватает? Или это просто невозможно так выразить?
c#
linq
async-await
Мэтт Джонсон-Пинт
источник
источник
DoSomethingAsync(foo)
последовательно обрабатывать каждый foo, или это кандидат на Parallel.ForEach <Foo> ?Parallel.ForEach
это блокировка. Шаблон здесь взят из видео Джона Скита об асинхронном C # на Pluralsight . Он выполняется параллельно без блокировки..ToList()
если я просто собираюсь использоватьWhenAll
.)DoSomethingAsync
написано, список может выполняться или не выполняться параллельно. Я смог написать тестовый метод, который был, и версию, которой не было, но в любом случае поведение продиктовано самим методом, а не делегатом, создавшим задачу. Извините за путаницу. Однако, еслиDoSomethingAsyc
возвращаетсяTask<Foo>
, тоawait
in the delegate не является абсолютно необходимым ... Я думаю, что это было основным моментом, который я собирался попытаться высказать.Ответы:
LINQ плохо работает с
async
кодом, но вы можете сделать это:var tasks = foos.Select(DoSomethingAsync).ToList(); await Task.WhenAll(tasks);
Если все ваши задачи возвращают один и тот же тип значения, вы даже можете сделать это:
var results = await Task.WhenAll(tasks);
что довольно приятно.
WhenAll
возвращает массив, поэтому я считаю, что ваш метод может возвращать результаты напрямую:return await Task.WhenAll(tasks);
источник
var tasks = foos.Select(foo => DoSomethingAsync(foo)).ToList();
var tasks = foos.Select(DoSomethingAsync).ToList();
Select
. Но большинству не нравитсяWhere
.async
для сокращения потоков; если он привязан к процессору и уже находится в фоновом потоке, тоasync
не принесет никакой пользы.Чтобы расширить ответ Стивена, я создал следующий метод расширения, чтобы сохранить свободный стиль 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); } } }
источник
ToArrayAsync
Одна проблема с 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 событий.
источник
Используйте
Task.WaitAll
или вTask.WhenAll
зависимости от того, что вам нужно.источник
Task.WaitAll
блокирует, не ожидает и не работает сTask<T>
.WhenAll
?Task.WhenAll должен помочь здесь.
источник