Skip to main content

Async e Await | CSharp

  • Async e Await | CSharp
warning
  • 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)
    • alt text
danger
  • Evite utilizar .Result ou .Wait()
  • Utilize Task.WhenAll para operações assíncronas que podem ser executadas de forma paralela.
  • Evite utilizar async void
  • Utilize ValueTask para 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

  1. async: A palavra-chave async é 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.
  2. await: A palavra-chave await é usada dentro de uma função assíncrona para indicar que uma operação assíncrona deve ser aguardada. Quando você coloca await antes 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

warning

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 de Task2.
  • Task2 lança uma exceção, que é imediatamente propagada para Task1 (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!".
  • Task2 ainda está rodando em background.
  • Após 2 segundos, Task2 falha e lança uma exceção.
  • Como Task1 já foi finalizado, a exceção de Task2 não é propagada para Task1.
  • 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

  • Task2 inicia e Task1 continua sua execução.
  • Task2 lança uma exceção após 1 segundo.
  • Task1 ainda não finalizou porque está bloqueado pelo Thread.Sleep(3000).
  • O erro de Task2 aparece 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}");
}
});