Gostaria de perguntar sua opinião sobre a arquitetura correta quando usar Task.Run
. Estou com a interface do usuário atrasada em nosso aplicativo WPF .NET 4.5 (com estrutura Caliburn Micro).
Basicamente, estou fazendo (trechos de código muito simplificados):
public class PageViewModel : IHandle<SomeMessage>
{
...
public async void Handle(SomeMessage message)
{
ShowLoadingAnimation();
// Makes UI very laggy, but still not dead
await this.contentLoader.LoadContentAsync();
HideLoadingAnimation();
}
}
public class ContentLoader
{
public async Task LoadContentAsync()
{
await DoCpuBoundWorkAsync();
await DoIoBoundWorkAsync();
await DoCpuBoundWorkAsync();
// I am not really sure what all I can consider as CPU bound as slowing down the UI
await DoSomeOtherWorkAsync();
}
}
Pelos artigos / vídeos que li / vi, sei que await
async
não é necessariamente executado em um thread em segundo plano e para iniciar o trabalho em segundo plano, você precisa envolvê-lo em espera Task.Run(async () => ... )
. O uso async
await
não bloqueia a interface do usuário, mas continua sendo executado no encadeamento da interface do usuário, tornando-o lento.
Onde é o melhor lugar para colocar o Task.Run?
Devo apenas
Encerre a chamada externa porque isso é menos trabalhoso para o .NET
, ou devo agrupar apenas métodos vinculados à CPU em execução internamente,
Task.Run
pois isso o reutiliza para outros lugares? Não tenho certeza aqui se iniciar um trabalho em threads de fundo no fundo é uma boa idéia.
Anúncio (1), a primeira solução seria assim:
public async void Handle(SomeMessage message)
{
ShowLoadingAnimation();
await Task.Run(async () => await this.contentLoader.LoadContentAsync());
HideLoadingAnimation();
}
// Other methods do not use Task.Run as everything regardless
// if I/O or CPU bound would now run in the background.
Anúncio (2), a segunda solução seria assim:
public async Task DoCpuBoundWorkAsync()
{
await Task.Run(() => {
// Do lot of work here
});
}
public async Task DoSomeOtherWorkAsync(
{
// I am not sure how to handle this methods -
// probably need to test one by one, if it is slowing down UI
}
fonte
await Task.Run(async () => await this.contentLoader.LoadContentAsync());
deve ser simplesmenteawait Task.Run( () => this.contentLoader.LoadContentAsync() );
. AFAIK você não ganha nada adicionando um segundoawait
easync
dentroTask.Run
. E como você não está transmitindo parâmetros, isso simplifica um pouco mais aawait Task.Run( this.contentLoader.LoadContentAsync );
.Respostas:
Observe as diretrizes para executar o trabalho em um thread da interface do usuário , coletado no meu blog:
Existem duas técnicas que você deve usar:
1) Use
ConfigureAwait(false)
quando puder.Por exemplo, em
await MyAsync().ConfigureAwait(false);
vez deawait MyAsync();
.ConfigureAwait(false)
informaawait
que você não precisa continuar no contexto atual (nesse caso, "no contexto atual" significa "no encadeamento da interface do usuário"). No entanto, para o restante desseasync
método (após oConfigureAwait
), você não pode fazer nada que pressuponha que você esteja no contexto atual (por exemplo, atualize os elementos da interface do usuário).Para obter mais informações, consulte o artigo Práticas recomendadas do MSDN em programação assíncrona .
2) Use
Task.Run
para chamar métodos ligados à CPU.Você deve usar
Task.Run
, mas não dentro de qualquer código que deseja que seja reutilizável (ou seja, código de biblioteca). Então você usaTask.Run
para chamar o método, não como parte da implementação do método.Portanto, o trabalho puramente vinculado à CPU ficaria assim:
Você chamaria usando
Task.Run
:Os métodos que são uma mistura de limite de CPU e de E / S devem ter uma
Async
assinatura com documentação apontando sua natureza de limite de CPU:Você também chamaria de usar
Task.Run
(já que é parcialmente vinculado à CPU):fonte
ConfigureAwait(false)
. Se você fizer isso primeiro, poderá achar que issoTask.Run
é completamente desnecessário. Se você não ainda precisaTask.Run
, então não faz muita diferença para o tempo de execução, neste caso, se você chamá-lo uma vez ou muitas vezes, por isso basta fazer o que é mais natural para o seu código.ConfigureAwait(false)
no método vinculado à CPU, ainda será o thread da interface do usuário que fará o método vinculado à CPU, e somente tudo o que estiver a seguir poderá ser feito em um thread TP. Ou entendi algo errado?Task.Run
entende assinaturas assíncronas, portanto não será concluída até queDoWorkAsync
seja concluída. O extraasync
/await
é desnecessário. Eu explico mais sobre o "porquê" na minha série de blogs sobreTask.Run
etiqueta .TaskCompletionSource<T>
ou uma de suas notações abreviadas, comoFromAsync
. Eu tenho uma postagem no blog que entra em mais detalhes por que os métodos assíncronos não requerem threads .Um problema com o ContentLoader é que internamente ele opera sequencialmente. Um padrão melhor é paralelizar o trabalho e depois sincronizar no final, para obtermos
Obviamente, isso não funciona se alguma das tarefas exigir dados de outras tarefas anteriores, mas deve oferecer melhor rendimento geral para a maioria dos cenários.
fonte