Existe algum cenário em que escrever método como este:
public async Task<SomeResult> DoSomethingAsync()
{
// Some synchronous code might or might not be here... //
return await DoAnotherThingAsync();
}
em vez disso:
public Task<SomeResult> DoSomethingAsync()
{
// Some synchronous code might or might not be here... //
return DoAnotherThingAsync();
}
faria sentido?
Por que usar return await
construct quando você pode retornar diretamente Task<T>
da DoAnotherThingAsync()
invocação interna ?
Eu vejo código return await
em muitos lugares, acho que posso ter perdido alguma coisa. Mas, tanto quanto eu entendo, não usar palavras-chave async / wait neste caso e retornar diretamente a tarefa seria funcionalmente equivalente. Por que adicionar sobrecarga adicional de await
camada adicional ?
c#
.net
.net-4.5
async-await
TX_
fonte
fonte
Respostas:
Há um caso sorrateiro quando
return
no método normal ereturn await
noasync
método se comporta de maneira diferente: quando combinado comusing
(ou, mais geralmente,return await
em umtry
bloco).Considere estas duas versões de um método:
O primeiro método fará
Dispose()
oFoo
objeto assim que oDoAnotherThingAsync()
método retornar, o que provavelmente é muito antes de ele realmente ser concluído. Isso significa que a primeira versão provavelmente está com erros (porqueFoo
é descartada muito cedo), enquanto a segunda versão funcionará bem.fonte
foo.DoAnotherThingAsync().ContinueWith(_ => foo.Dispose());
Dispose()
retornavoid
. Você precisaria de algo parecidoreturn foo.DoAnotherThingAsync().ContinueWith(t -> { foo.Dispose(); return t.Result; });
. Mas não sei por que você faria isso quando pode usar a segunda opção.{ var task = DoAnotherThingAsync(); task.ContinueWith(_ => foo.Dispose()); return task; }
. O caso de uso é bastante simples: se você estiver no .NET 4.0 (como a maioria), ainda poderá escrever código assíncrono dessa maneira, que funcionará bem com os aplicativos 4.5.Foo
somente depois que o retorno éTask
concluído, o que eu não gosto, porque introduz desnecessariamente a simultaneidade.Se você não precisar
async
(por exemplo, você pode retornar oTask
diretamente), não useasync
.Há algumas situações em que
return await
é útil, como se você tiver duas operações assíncronas para fazer:Para
async
saber mais sobre desempenho, consulte o artigo e o vídeo de Stephen Toub no MSDN sobre o tópico.Atualização: escrevi uma postagem de blog que entra em muito mais detalhes.
fonte
await
é útil no segundo caso? Por que não fazerreturn SecondAwait(intermediate);
?return SecondAwait(intermediate);
alcançaria o objetivo? Eu acho quereturn await
é redundante aqui também ...await
na primeira linha, também deve usá-lo na segunda.async
versão usará menos recursos (threads) que a versão síncrona.SecondAwait
é `string, a mensagem de erro é:" CS4016: Como esse é um método assíncrono, a expressão de retorno deve ser do tipo 'string' em vez de 'Task <string>' ".O único motivo para você querer fazer isso é se houver algum outro
await
no código anterior ou se você estiver de alguma forma manipulando o resultado antes de devolvê-lo. Outra maneira pela qual isso pode estar acontecendo é através de umatry/catch
alteração na maneira como as exceções são tratadas. Se você não está fazendo nada disso, está certo, não há razão para adicionar a sobrecarga de criar o métodoasync
.fonte
return await
seria necessário (em vez de apenas retornar a tarefa de invocação de filhos), mesmo que haja mais alguma espera no código anterior . Poderia, por favor, fornecer uma explicação?async
, como aguardaria a primeira tarefa? Você precisa marcar o método comoasync
se quisesse usar qualquer espera. Se o método estiver marcado comoasync
e você tiver umawait
código anterior, precisaráawait
da segunda operação assíncrona para que ele seja do tipo apropriado. Se você apenas removesseawait
, ele não seria compilado, pois o valor de retorno não seria do tipo apropriado. Desde que o método éasync
o resultado é sempre envolvido em uma tarefa.async
método você não retorna uma tarefa, você retorna o resultado da tarefa que será então quebrada.Task<Type>
explicitamente, enquantoasync
ditamos a retornarType
(na qual o próprio compilador se transformariaTask<Type>
).async
é apenas um açúcar sintático para ligar explicitamente as continuações. Você não precisaasync
fazer nada, mas ao realizar praticamente qualquer operação assíncrona não trivial, é muito mais fácil trabalhar com isso. Por exemplo, o código que você forneceu não propaga erros da maneira que você deseja, e fazê-lo corretamente em situações ainda mais complexas começa a ficar muito mais difícil. Embora você nunca preciseasync
, as situações que descrevo são onde está agregando valor para usá-lo.Outro caso em que você pode precisar aguardar o resultado é este:
Nesse caso,
GetIFooAsync()
deve aguardar o resultado de,GetFooAsync
porque o tipo deT
é diferente entre os dois métodos eTask<Foo>
não é diretamente atribuível aTask<IFoo>
. Mas se você aguardar o resultado, ele se tornará oFoo
que é diretamente atribuívelIFoo
. Em seguida, o método assíncrono apenas reembala o resultado por dentroTask<IFoo>
e por fora.fonte
Task<>
é invariável.Tornar o método "thunk" simples de outra forma assíncrono cria uma máquina de estado assíncrona na memória, enquanto a não assíncrona não. Embora isso possa apontar as pessoas para o uso da versão não assíncrona porque é mais eficiente (o que é verdade), isso também significa que, no caso de um travamento, você não tem evidências de que esse método esteja envolvido na "pilha de retorno / continuação" o que às vezes torna mais difícil entender o jeito.
Então, sim, quando o perf não for crítico (e geralmente não é), lançarei o assíncrono em todos esses métodos de thunk, para que eu tenha a máquina de estado assíncrona para me ajudar a diagnosticar travamentos mais tarde e também para garantir que, se esses Se os métodos thunk evoluírem com o tempo, eles retornarão tarefas com falha em vez de jogar.
fonte
Se você não usar o retorno aguardar, poderá arruinar o rastreamento da pilha durante a depuração ou quando ele for impresso nos logs com exceções.
Quando você retorna a tarefa, o método cumpriu sua finalidade e está fora da pilha de chamadas. Quando você usa
return await
você o deixa na pilha de chamadas.Por exemplo:
Pilha de chamadas ao usar a espera: A aguardando a tarefa de B => B aguardando a tarefa de C
Pilha de chamadas quando não estiver usando aguardar: A aguardando a tarefa de C, que B retornou.
fonte
Isso também me confunde e sinto que as respostas anteriores ignoraram sua pergunta real:
Bem, às vezes você realmente deseja um
Task<SomeType>
, mas na maioria das vezes deseja uma instância doSomeType
resultado da tarefa.Do seu código:
Uma pessoa não familiarizada com a sintaxe (eu, por exemplo) pode pensar que esse método deve retornar a
Task<SomeResult>
, mas como está marcado comasync
, significa que seu tipo de retorno real éSomeResult
. Se você apenas usarreturn foo.DoAnotherThingAsync()
, retornará uma tarefa que não será compilada. A maneira correta é retornar o resultado da tarefa, então oreturn await
.fonte
var task = DoSomethingAsync();
, você daria uma tarefa, nãoT
async/await
coisa. Para meu entendimentoTask task = DoSomethingAsync()
, enquantoSomething something = await DoSomethingAsync()
ambos trabalham. O primeiro fornece a tarefa adequada, enquanto o segundo, devido àawait
palavra - chave, fornece o resultado da tarefa após a conclusão. Eu poderia, por exemplo, terTask task = DoSomethingAsync(); Something something = await task;
.