Preciso modificar um programa existente e ele contém o seguinte código:
var inputs = events.Select(async ev => await ProcessEventAsync(ev))
.Select(t => t.Result)
.Where(i => i != null)
.ToList();
Mas isso me parece muito estranho, antes de tudo, o uso de async
e await
no select. De acordo com esta resposta de Stephen Cleary, eu deveria ser capaz de abandoná-las.
Então o segundo Select
que seleciona o resultado. Isso não significa que a tarefa não é assíncrona e é executada de forma síncrona (muito esforço por nada) ou será executada de forma assíncrona e quando concluída, o restante da consulta é executado?
Devo escrever o código acima como segue, de acordo com outra resposta de Stephen Cleary :
var tasks = await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev)));
var inputs = tasks.Where(result => result != null).ToList();
e é completamente o mesmo assim?
var inputs = (await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev))))
.Where(result => result != null).ToList();
Enquanto estou trabalhando neste projeto, gostaria de alterar o primeiro exemplo de código, mas não estou muito interessado em alterar (aparentemente trabalhando) código assíncrono. Talvez eu esteja apenas me preocupando por nada e todos os três exemplos de código façam exatamente a mesma coisa?
ProcessEventsAsync tem esta aparência:
async Task<InputResult> ProcessEventAsync(InputEvent ev) {...}
fonte
Task<InputResult>
comoInputResult
ser uma classe personalizada.Select
os resultados das tarefas anteriores à suaWhere
.Result
propriedade da tarefa #Respostas:
A chamada para
Select
é válida. Essas duas linhas são essencialmente idênticas:(Há uma pequena diferença em relação a como uma exceção síncrona seria lançada
ProcessEventAsync
, mas no contexto desse código isso não importa.)Isso significa que a consulta está bloqueando. Portanto, não é realmente assíncrono.
Dividindo:
primeiro iniciará uma operação assíncrona para cada evento. Então esta linha:
esperará que essas operações sejam concluídas uma de cada vez (primeiro aguarda a operação do primeiro evento, depois o próximo, depois o próximo, etc.).
Esta é a parte da qual não me importo, porque bloqueia e também inclui exceções
AggregateException
.Sim, esses dois exemplos são equivalentes. Os dois iniciam todas as operações assíncronas (
events.Select(...)
), aguardam de forma assíncrona que todas as operações sejam concluídas em qualquer ordem (await Task.WhenAll(...)
) e depois prosseguem com o restante do trabalho (Where...
).Ambos os exemplos são diferentes do código original. O código original está bloqueando e incluirá exceções
AggregateException
.fonte
AggregateException
eu obteria várias exceções separadas no segundo código?Result
isso seria envolvidoAggregateException
.stuff.Select(x => x.Result);
withawait Task.WhenAll(stuff)
O código existente está funcionando, mas está bloqueando o encadeamento.
cria uma nova tarefa para cada evento, mas
bloqueia o encadeamento aguardando o término de cada nova tarefa.
Por outro lado, seu código produz o mesmo resultado, mas permanece assíncrono.
Apenas um comentário no seu primeiro código. Está linha
produzirá uma única tarefa para que a variável seja nomeada no singular.
Finalmente, seu último código faz o mesmo, mas é mais sucinto
Para referência: Task.Wait / Task.WhenAll
fonte
tasks
variável, você está completamente certo. Escolha horrível, nem sequer são tarefas, pois são aguardadas imediatamente. Só vou deixar a pergunta como éCom os métodos atuais disponíveis no Linq, parece bastante feio:
Esperamos que as seguintes versões do .NET apresentem ferramentas mais elegantes para lidar com coleções de tarefas e tarefas de coleções.
fonte
Eu usei este código:
como isso:
fonte
Func<TSource, Task<TResult>> method
conter oother params
mencionado no segundo bit de código?Select()
, assim como um elegante comparecimento.Eu prefiro isso como um método de extensão:
Para que seja utilizável com o encadeamento de métodos:
fonte
Wait
quando ele não estiver realmente esperando. Está criando uma tarefa que está completa quando todas as tarefas estão completas. Chame-oWhenAll
, como oTask
método que ele emula. Também não faz sentido o método serasync
. Basta ligarWhenAll
e terminar com isso.WhenAll
retorna uma lista avaliada (não é avaliada preguiçosamente), um argumento pode ser usado para usar oTask<T[]>
tipo de retorno para significar isso. Quando aguardado, ele ainda poderá usar o Linq, mas também comunicará que não é preguiçoso.