Eu tenho uma lista de tarefas que criei assim:
public async Task<IList<Foo>> GetFoosAndDoSomethingAsync()
{
var foos = await GetFoosAsync();
var tasks = foos.Select(async foo => await DoSomethingAsync(foo)).ToList();
...
}
Ao usar .ToList()
, todas as tarefas devem ser iniciadas. Agora quero aguardar sua finalização e retornar os resultados.
Isso funciona no ...
bloco acima :
var list = new List<Foo>();
foreach (var task in tasks)
list.Add(await task);
return list;
Ele faz o que eu quero, mas parece um tanto desajeitado. Prefiro escrever algo mais simples como este:
return tasks.Select(async task => await task).ToList();
... mas isso não compila. o que estou perdendo? Ou simplesmente não é possível expressar as coisas dessa maneira?
c#
linq
async-await
Matt Johnson-Pint
fonte
fonte
DoSomethingAsync(foo)
série para cada foo ou este é um candidato para Parallel.ForEach <Foo> ?Parallel.ForEach
está bloqueando. O padrão aqui vem do vídeo Asynchronous C # de Jon Skeet no Pluralsight . Executa em paralelo sem bloqueio..ToList()
se for apenas usarWhenAll
.)DoSomethingAsync
é escrita, a lista pode ou não ser executada em paralelo. Consegui escrever um método de teste que era e uma versão que não era, mas em ambos os casos o comportamento é ditado pelo próprio método, não pelo delegado que cria a tarefa. Desculpe pela confusão. Porém, seDoSomethingAsyc
retornarTask<Foo>
, então oawait
in no delegado não é absolutamente necessário ... Acho que esse era o ponto principal que eu ia tentar fazer.Respostas:
LINQ não funciona perfeitamente com
async
código, mas você pode fazer isso:var tasks = foos.Select(DoSomethingAsync).ToList(); await Task.WhenAll(tasks);
Se todas as suas tarefas retornarem o mesmo tipo de valor, você pode até fazer isso:
var results = await Task.WhenAll(tasks);
o que é muito bom.
WhenAll
retorna uma matriz, então acredito que seu método pode retornar os resultados diretamente:return await Task.WhenAll(tasks);
fonte
var tasks = foos.Select(foo => DoSomethingAsync(foo)).ToList();
var tasks = foos.Select(DoSomethingAsync).ToList();
Select
. Mas a maioria não gostaWhere
.async
para reduzir threads; se estiver vinculado à CPU e já estiver em um thread de segundo plano,async
não fornecerá nenhum benefício.Para expandir a resposta de Stephen, criei o seguinte método de extensão para manter o estilo fluente do LINQ. Você pode então fazer
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); } } }
fonte
ToArrayAsync
Um problema com Task.WhenAll é que ele criaria um paralelismo. Na maioria dos casos, pode ser ainda melhor, mas às vezes você deseja evitar. Por exemplo, ler dados em lotes do banco de dados e enviar dados para algum serviço remoto da web. Você não deseja carregar todos os lotes para a memória, mas acessar o banco de dados assim que o lote anterior for processado. Então, você tem que quebrar a assincronicidade. Aqui está um exemplo:
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) { }
Observação .GetAwaiter (). GetResult () convertendo-o em synchronose. O DB seria atingido lentamente apenas depois que batchSize de eventos fosse processado.
fonte
Use
Task.WaitAll
ou oTask.WhenAll
que for apropriado.fonte
Task.WaitAll
está bloqueando, não está aguardando e não funcionará com aTask<T>
.WhenAll
?Task.WhenAll deve fazer o truque aqui.
fonte