Async/Await & Tasks
State machine, I/O-bound vs CPU-bound, cancelamento e paralelismo com Tasks
async/await compoe Tasks para que trabalho I/O-bound (HTTP, DB, arquivo, sockets) rode sem bloquear threads. O compilador gera uma state machine que pausa no await e resume quando a Task completa. Como funciona a state machine
Quando voce marca um metodo com async, o compilador transforma o corpo em uma state machine. Cada await e um ponto de suspensao: se a operacao I/O ainda nao completou, a thread e liberada e o metodo retorna uma Task. Quando o I/O completa, a continuacao e agendada e o metodo resume de onde parou.
Otimizacao "fast path": se a
Task awaitada ja esta completa no momento do await (cache hit, sync completion, ValueTask sincrono), o compilador nao aloca a state machine no heap nem agenda continuacao β o metodo executa todo na stack como codigo sincrono normal. Essa e a razao por que ValueTask<T> existe: quando o caso comum e "ja temos a resposta", evita alocar uma Task de heap por chamada. Em hot path com cache hit, e a diferenca entre alocar zero ou alocar uma Task por request. I/O-bound vs CPU-bound
A distincao e fundamental para escolher entre async/await e Task.Run.
| Tipo | Exemplos | Abordagem | Bloqueia thread? |
|---|---|---|---|
| I/O-bound | HTTP, banco de dados, arquivo, socket | async/await | Nao |
| CPU-bound | Calculo pesado, criptografia, parsing | Task.Run (offload para ThreadPool) | Sim (na worker thread) |
Task vs ValueTask
| Aspecto | Task<T> | ValueTask<T> |
|---|---|---|
| Alocacao | Sempre aloca no heap | Stack quando sincrono, heap quando async |
| Quando usar | Padrao para a maioria dos cenarios | Hot path com resultado sincrono frequente |
| Pode awaitar multiplas vezes? | Sim | Nao |
| Regra | Use por padrao | Use somente quando mediu que ajuda |
Retorno de metodos async
| Retorno | Quando usar | Pode awaitar? | Propaga excecao? |
|---|---|---|---|
Task | Metodo async sem retorno | Sim | Sim |
Task<T> | Metodo async com retorno | Sim | Sim |
async void | Somente event handlers | Nao | Nao (crash) |
Por que nao se deve usar async void?
async void nao retorna Task, entao o chamador nao pode awaitar nem observar excecoes. Se uma excecao ocorre, ela vai para o SynchronizationContext e geralmente crasha o processo. A unica excecao e em event handlers (ex: Button_Click), onde a assinatura e imposta pelo framework. Em qualquer outro caso, retorne Task. O que acontece se voce esquecer de awaitar uma Task?
A Task roda em "fire-and-forget" β excecoes sao silenciosamente engolidas (nao propagam para o chamador). Em .NET 4+, Tasks nao observadas nao crasham o processo, mas voce perde o controle de erros, ordering e lifetime. O compilador emite warning CS4014 para alertar. Sempre awaite ou armazene a Task explicitamente com descarte (
_ = DoAsync()). ConfigureAwait(false) β quando e por que?
Por padrao,
await captura o SynchronizationContext e resume no mesmo contexto (ex: UI thread). ConfigureAwait(false) pula essa captura β a continuacao roda em qualquer thread do pool. Use em codigo de biblioteca para evitar deadlocks e reduzir overhead. ASP.NET Core nao tem SynchronizationContext de jeito nenhum β ConfigureAwait(false) dentro de uma controller/minimal API e um no-op (nao quebra nada, so nao faz nada). Em WPF/WinForms/MAUI/ASP.NET Framework, sem ConfigureAwait(false) em bibliotecas chamadas com .Result/.Wait() voce deadlocka: a continuacao quer voltar ao contexto original, que esta bloqueado esperando o resultado. Em bibliotecas redistribuidas, sempre use ConfigureAwait(false) β voce nao sabe quem vai chamar.