Neste artigo vamos abordar os conceitos básicos e a utilização de tarefas assíncronas no C#, mostrando duas formas distintas de se trabalhar e quais são suas diferenças.
Vamos começar criando um trecho de código que execute de forma síncrona, e entender como ele é executado.
[HttpGet("TesteSincrono")]
public IActionResult TesteSincrono()
{
Console.WriteLine("Iniciando teste síncrono");
var resultadoApi1 = ApiConsulta1();
var resultadoApi2 = ApiConsulta2();
var resultadoApi3 = ApiConsulta3();
Console.WriteLine("Finalizando teste síncrono");
return Ok();
}
private string ApiConsulta1()
{
Console.WriteLine("Iniciando ApiConsulta1");
//simulando uma chamada de 1 segundo
Console.WriteLine("Executando chamada ApiConsulta1");
Thread.Sleep(1000);
Console.WriteLine("Finalizando ApiConsulta1");
return "1"; //simulando um retorno
}
private string ApiConsulta2()
{
Console.WriteLine("Iniciando ApiConsulta2");
//simulando uma chamada de 2 segundos
Console.WriteLine("Executando chamada ApiConsulta2");
Thread.Sleep(2000);
Console.WriteLine("Finalizando ApiConsulta2");
return "2"; //simulando um retorno
}
private string ApiConsulta3()
{
Console.WriteLine("Iniciando ApiConsulta3");
//simulando uma chamada de 3 segundos
Console.WriteLine("Executando chamada ApiConsulta3");
Thread.Sleep(3000);
Console.WriteLine("Finalizando ApiConsulta3");
return "3"; //simulando um retorno
}
```
Então no exemplo acima criamos um endpoint onde simulamos consumir três apis distintas e colocamos para cada uma dessas apis um tempo de espera de 1 segundo à 3 segundos que seria o tempo que a api levaria para nos responder, tudo isso para ficar evidente os resultados dos nossos testes.
Executando o código acima podemos ver que o endpoint levou **6.03 segundos** para ser executado e o output do console ficou assim:
![teste01](https://dev-to-uploads.s3.amazonaws.com/i/bl73kwupdw1sb21cc6rb.PNG)
Note que foi executado um método por vez, esperando sua execução ser finalizada para que o próximo método fosse iniciado. Essa forma de executar não está errada, porém deixamos a aplicação ociosa por algum tempo aguardando o retorno dos métodos e a thread principal da aplicação "travada".
## Executando tarefas assíncronas
Agora vamos utilizar o mesmo cenário de exemplo porém vamos executar os métodos que simulam uma chamada de api em uma task, sendo assim teremos o seguinte código:
```csharp
[HttpGet("TesteAssincrono")]
public IActionResult TesteAssincrono()
{
Console.WriteLine("Iniciando teste assíncrono");
var apiConsulta1Task = Task.Run(() => ApiConsulta1());
var apiConsulta2Task = Task.Run(() => ApiConsulta2());
var apiConsulta3Task = Task.Run(() => ApiConsulta3());
var resultadoApi1 = apiConsulta1Task.Result;
var resultadoApi2 = apiConsulta2Task.Result;
var resultadoApi3 = apiConsulta3Task.Result;
Console.WriteLine("Finalizando teste assíncrono");
return Ok();
}
```
Note no trecho de código acima que colocamos as chamadas dos métodos de api dentro de uma **Task.Run()**, isso faz com que estes métodos sejam chamados em **paralelo** através de threads separadas da thread principal da aplicação. **Tasks são executadas de forma assíncrona**, sendo assim neste exemplo a thread principal da aplicação continuará executando os demais comandos enquanto as Tasks são executadas em threads separadas em paralelo. No momento em que executamos **Task.Result** colocamos a thread principal da aplicação esperando pelo resultado da Task, para que possamos manipular o retorno dela.
Executando o código acima podemos ver que o endpoint levou **3.03 segundos** para ser executado e o output do console ficou assim:
![teste02](https://dev-to-uploads.s3.amazonaws.com/i/1oobh1yy1jc36lknaofv.PNG)
Podemos observar que a aplicação não aguardou o primeiro método ser finalizado e já iniciou os demais, fazendo com que a aplicação demore menos tempo para executar todos os comandos, outro ponto interessante que por se tratar de paralelismo, a execução do método ApiConsulta3 ocorreu antes da execução do método ApiConsulta2, sendo que no código chamamos a ApiConsulta2 antes.
## Executando tarefas assíncronas com async/await
Outra forma de trabalharmos de forma assíncrona é utilizando as palavras chaves **async** e **await** em métodos que retornam uma **Task**, vamos aplicar no mesmo cenário de exemplo:
```csharp
[HttpGet("TesteAssincronoAsyncAwait")]
public async Task<IActionResult> TesteAssincronoAsyncAwait()
{
Console.WriteLine("Iniciando teste assíncrono com async/await");
var apiConsulta1Task = ApiConsulta1Async();
var apiConsulta2Task = ApiConsulta2Async();
var apiConsulta3Task = ApiConsulta3Async();
var resultadoApi1 = await apiConsulta1Task;
var resultadoApi2 = await apiConsulta2Task;
var resultadoApi3 = await apiConsulta3Task;
Console.WriteLine("Finalizando teste assíncrono com async/await");
return Ok();
}
private async Task<string> ApiConsulta1Async()
{
Console.WriteLine("Iniciando ApiConsulta1Async");
//simulando uma chamada de 1 segundo
Console.WriteLine("Executando chamada ApiConsulta1Async");
await Task.Delay(1000);
Console.WriteLine("Finalizando ApiConsulta1Async");
return "1"; //simulando um retorno
}
private async Task<string> ApiConsulta2Async()
{
Console.WriteLine("Iniciando ApiConsulta2Async");
//simulando uma chamada de 2 segundos
Console.WriteLine("Executando chamada ApiConsulta2Async");
await Task.Delay(2000);
Console.WriteLine("Finalizando ApiConsulta2Async");
return "2"; //simulando um retorno
}
private async Task<string> ApiConsulta3Async()
{
Console.WriteLine("Iniciando ApiConsulta3Async");
//simulando uma chamada de 3 segundos
Console.WriteLine("Executando chamada ApiConsulta3Async");
await Task.Delay(3000);
Console.WriteLine("Finalizando ApiConsulta3Async");
return "3"; //simulando um retorno
}
```
Como podemos ver, agora nossos métodos que simulam uma consulta de api retornam Tasks e para esperarmos o retorno da execução delas colocamos a palavra chave **await** na frente, sendo assim todo método que utilizar a palavra chave await dentro dele precisa ser declarado com a palavra chave **async**, como no nosso exemplo:
`public async Task<IActionResult> TesteAssincronoAsyncAwait()`
Executando o código acima podemos ver que o endpoint levou **3.03 segundos** também para ser executado e o output do console ficou assim:
![teste03](https://dev-to-uploads.s3.amazonaws.com/i/y8bhplyurin88mn42ua6.PNG)
Este exemplo da forma que foi escrito também levou menos tempo para ser totalmente executado, então podemos dizer que utilizando as palavras chaves async e await executamos Tasks em threads separadas em paralelo da thread principal?
A resposta é **não**, essa forma de trabalhar apesar de também ser assíncrona não utiliza paralelismo, o que ocorre é que ao executar um método **async Task** a thread principal continua disponível para prosseguir executando outros comandos enquanto não precisarmos do retorno do método. Diferente do teste que fizemos de forma síncrona que apesar de também só utilizar a thread principal da aplicação, no teste síncrono a thread principal ficou aguardando o retorno do método para prosseguir executando os demais comandos.
Agora uma última curiosidade, executando o exemplo acima dessa forma aqui:
```csharp
[HttpGet("TesteAssincronoAsyncAwait")]
public async Task<IActionResult> TesteAssincronoAsyncAwait()
{
Console.WriteLine("Iniciando teste assíncrono com async/await");
var resultadoApi1 = await ApiConsulta1Async();
var resultadoApi2 = await ApiConsulta2Async();
var resultadoApi3 = await ApiConsulta3Async();
Console.WriteLine("Finalizando teste assíncrono com async/await");
return Ok();
}
```
O código levará 6 segundos novamente para ser executado, isso porque a cada execução aos métodos que simulam uma consulta de api já colocamos a thread principal para aguardar o retorno com o await, então mesmo a Task sendo executada de forma assíncrona, não ganhamos otimização de tempo de execução, porém ainda assim é uma abordagem melhor do que trabalhar de forma síncrona porque deixamos a nossa thread principal disponível.
Espero que este artigo possa ajudar a esclarecer melhor a utilização de Tasks em C#, sintam-se a vontade para debater sobre o assunto aqui.
Referência:
[Programação assíncrona com async e await](https://docs.microsoft.com/pt-br/dotnet/csharp/programming-guide/concepts/async/)
Top comments (1)
Valeu! Muito esclarecedor, não é bicho de 7 cabeças quanto parece ser