.NET
- Criando métodos assíncronos falsos com Task.Run()
![]() |
Seria possível criar um invólucro assíncrono usando o método Task.Run() e assim tornar assíncrono um método síncrono ? |
Vamos esclarecer um conceito importante : programação assíncrona não é mesma coisa que programação paralela.
Confundir os dois
é um erro fácil de cometer pois ambos os conceitos estão associados com o objeto
Task.
A diferença essencial é que uma operação assíncrona esta relacionada com qual recurso você consome enquanto a execução em paralelo esta relacionada com a quantidade de recursos que você usa.
Vejamos o trecho de código a seguir:
public async Task<string> MinhaTaskAsync(Uri endereco)
{ return await Task.Run(() => { using (var client = new WebClient()) { return client.DownloadString(endereco); } }); } |
Este código tenta converter um método síncrono em algo assíncrono envolvendo a operação com um Task.Run();
Apesar disto o
método ainda é executado de forma síncrona, sendo apenas executado em uma
thread em segundo plano. Então essa abordagem não melhora a escalabilidade do
código, já que você está realmente consumindo mais recursos usando uma thread
do pool de threads. Isso é menos escalonável do que executar coisas de forma síncrona,
pois você está usando mais recursos do que precisa.
O objetivo principal de Task.Run() é executar
código vinculado à CPU de maneira assíncrona. Ele faz isso usando uma thread
do pool de threads para executar o método e retornar uma
Task para representar a conclusão do método.
Se você envolver o
trabalho vinculado a uma operação I/O com Task.Run(),
você estará apenas usando uma nova thread para executar o código de forma
síncrona. Ele pode ter uma assinatura semelhante porque está retornando uma
Task, mas tudo o que você está fazendo é bloquear uma thread diferente.
Nesse sentido, o uso de Task.Run() cria um método
assíncrono “falso”. Parece um método
assíncrono, mas na verdade é apenas uma simulação ao fazer o processamento em
uma thread em segundo plano. Os métodos assíncronos não tratam de
threads em segundo plano, mas fazem um uso mais eficiente da thread
atual.
Se você deseja obter os benefícios de escalabilidade do código assíncrono para
operações vinculadas a tarefas de I/O ou E/S, você precisa implementar o padrão
assíncrono usando as palavras-chave async e await.
Esses são, na verdade, atalhos para configurar callbacks para operações
de longa duração.
Um exemplo do mesmo código executado de forma assíncrona é mostrado a seguir:
public async Task<string> MinhaTaskAsync(Uri endereco) { using (var client = new HttpClient()) { return await client.GetStringAsync(address); } } |
Desta forma ao expor uma API com uma assinatura de método assíncrono, você está sinalizando aos consumidores que há benefícios de escalabilidade no uso do método.
Eles usarão o
método se estiverem preocupados com a escalabilidade, embora se estiverem mais
preocupados com a capacidade de resposta ou paralelismo, eles podem preferir
invocar um método síncrono em uma thread de segundo plano.
A questão é que a escolha deve ser deixada para o consumidor. Eles podem
implementar o método assíncrono, usar uma continuação para executar algum código
assim que a operação for concluída ou apenas executar uma versão síncrona se a
escalabilidade não for uma preocupação.
O que você não quer fazer é expor um método assíncrono "falso" que apenas envolve um conjunto de chamadas síncronas. Isso surpreende o usuário, oferecendo a eles algo que não é o que afirma ser, ou seja, um método assíncrono que realmente executa de forma síncrona.
Assim, seja honesto
com seus clientes....
"Disse-lhe Jesus: Porque me viste, Tomé, creste;
bem-aventurados os que não viram e creram."
João 20:29
Referências: