Async e Await | CSharp
- Async e Await | CSharp
- Possibilita realizar chamadas I/O sem bloquear a thread
- Aumenta a responsividade da aplicação, liberando threads para executar outros trabalhos
- Essencial para construir aplicações robustas que escalam
- I/O ou CPU-Bound - (I/O - Http request, abertura de arquivos) (CPU- Bound - Utiliza muito CPU)
- Evite utilizar
.Resultou.Wait() - Utilize
Task.WhenAllpara operações assíncronas que podem ser executadas de forma paralela. - Evite utilizar
async void - Utilize
ValueTaskpara operações que ocorrem com alta frequência e são, na maioria das vezes, síncronas.
Motivo de não usar o .Result
Ele não apenas bloqueia seu código forçando a execução síncrona, mas também impede que qualquer pessoa que esteja chamando seus métodos seja capaz de adiar a execução
async: A palavra-chaveasyncé usada para declarar que uma função é assíncrona. Isso permite que a função seja executada de maneira assíncrona, o que significa que ela pode ser pausada e continuada em vez de bloquear a execução do programa enquanto aguarda o término de uma operação demorada.await: A palavra-chaveawaité usada dentro de uma função assíncrona para indicar que uma operação assíncrona deve ser aguardada. Quando você colocaawaitantes de uma operação assíncrona (como uma chamada de função assíncrona), a execução da função é suspensa até que a operação seja concluída. Durante esse tempo, a thread pode ser liberada para fazer outras tarefas em vez de ser bloqueada.
Exceptions com Task
Isso serve para o Task.Run
Propagação de Exceções em Tasks dentro de uma API
Em uma API baseada em ASP.NET Core, o comportamento de propagação de exceções em métodos assíncronos (Task) depende diretamente do uso ou não do await. A seguir, detalhamos três cenários distintos e seus impactos no tratamento de erros.
Caso 1: Task1 chama Task2 com await e Task2 lança uma exceção
Se Task1 chamar Task2 utilizando await, a exceção gerada dentro de Task2 será propagada para Task1, permitindo que Task1 capture ou relance o erro.
Exemplo
[HttpGet("com-await")]
public async Task<IActionResult> ComAwait()
{
try
{
await Task2(); // A exceção de Task2 será lançada aqui
return Ok("Execução bem-sucedida!");
}
catch (Exception ex)
{
return StatusCode(500, $"Erro capturado na API: {ex.Message}");
}
}
async Task Task2()
{
throw new Exception("Erro na Task2");
}
Comportamento esperado
- A API aguarda (
await) a execução deTask2. Task2lança uma exceção, que é imediatamente propagada paraTask1(ComAwait).- Como a exceção é capturada no
try-catch, a API retorna um erro HTTP 500 com a mensagem do erro.
Caso 2: Task1 chama Task2 sem await, Task1 finaliza e Task2 lança uma exceção
Se Task1 chamar Task2 sem await, Task1 poderá concluir sua execução enquanto Task2 ainda está rodando em background. Se Task2 lançar uma exceção após Task1 já ter finalizado, essa exceção não será propagada para Task1, e poderá ser suprimida se não for corretamente capturada.
Exemplo
[HttpGet("sem-await")]
public IActionResult SemAwait()
{
Task2(); // Chamada sem await (Fire-and-Forget)
return Ok("Resposta enviada para o cliente!");
}
async Task Task2()
{
await Task.Delay(2000); // Simula um processamento longo
throw new Exception("Erro na Task2!");
}
Comportamento esperado
- O cliente recebe imediatamente a resposta
"Resposta enviada para o cliente!". Task2ainda está rodando em background.- Após 2 segundos,
Task2falha e lança uma exceção. - Como
Task1já foi finalizado, a exceção deTask2não é propagada paraTask1. - Dependendo do ambiente, a exceção pode ser completamente perdida ou ser registrada no log do servidor.
Caso 3: Task1 chama Task2 sem await, mas Task2 lança uma exceção antes de Task1 terminar
Se Task1 iniciar Task2 sem await, mas Task2 lançar uma exceção antes que Task1 finalize, a exceção será exibida no console, mas não será lançada dentro de Task1, pois Task1 não a está aguardando.
Exemplo
[HttpGet("sem-await-mas-rapido")]
public IActionResult SemAwaitMasRapido()
{
Task2(); // Fire-and-Forget
Thread.Sleep(3000); // Mantém a API "viva" por 3 segundos antes de responder
return Ok("Resposta enviada para o cliente!");
}
async Task Task2()
{
await Task.Delay(1000); // Menos tempo que a espera em Task1
throw new Exception("Erro na Task2!");
}
Comportamento esperado
Task2inicia eTask1continua sua execução.Task2lança uma exceção após 1 segundo.Task1ainda não finalizou porque está bloqueado peloThread.Sleep(3000).- O erro de
Task2aparece no console, mas não afeta a resposta da API. - Após os 3 segundos de
Thread.Sleep, a API retorna"Resposta enviada para o cliente!".
Considerações sobre APIs
Como em APIs a thread principal do servidor continua viva, se ocorrer um erro no Caso 2, o cliente já terá recebido a resposta, mas a exceção ainda poderá ser exibida no console ou log do servidor.
Para evitar que exceções sejam suprimidas, recomenda-se sempre capturar e logar exceções dentro das Tasks que não são aguardadas (Fire-and-Forget):
Task.Run(async () =>
{
try
{
await Task2();
}
catch (Exception ex)
{
Console.WriteLine($"Erro capturado no Fire-and-Forget: {ex.Message}");
}
});
