Propague async até a fronteira de I/O
Prefira APIs assíncronas de banco, HTTP e arquivos e propague async/await pela chamada. Em ASP.NET Core, não use .Result ou .Wait() para bloquear uma tarefa: sob concorrência, threads ficam ocupadas aguardando e o ThreadPool pode não acompanhar novas requisições.
Não transforme I/O síncrono em assíncrono apenas envolvendo a chamada em Task.Run. Isso desloca o bloqueio para outra thread, mas não libera o recurso nem cria uma operação I/O assíncrona.
Paralelize somente operações independentes
Se duas dependências não dependem uma da outra, iniciá-las antes de aguardar pode reduzir o tempo total da jornada. Passe o cancelamento e mantenha a concorrência compatível com os limites dessas dependências:
public async Task<ResumoResponse> ObterAsync(
int pedidoId,
CancellationToken cancellationToken)
{
var pedidoTask = _pedidos.ObterAsync(pedidoId, cancellationToken);
var statusTask = _pagamentos.ObterStatusAsync(pedidoId, cancellationToken);
await Task.WhenAll(pedidoTask, statusTask);
var pedido = await pedidoTask;
var status = await statusTask;
return new ResumoResponse(pedido, status);
}
O ganho depende da latência das dependências; a pressão sobre elas também aumenta. Não faça Task.WhenAll em uma lista ilimitada de itens sem limite de concorrência. Para lotes grandes, processe em janelas ou use um mecanismo de paralelismo limitado e monitore filas, conexões e erros.
Propague cancelamento e defina limites
Receba o CancellationToken da requisição e encaminhe-o a operações que aceitam cancelamento. Configure timeouts adequados para dependências e evite deixar trabalho continuar depois que a chamada perdeu sua utilidade. Cancelamento cooperativo não garante que todo driver interrompa imediatamente uma operação já enviada.
Retries podem multiplicar trabalho quando o sistema está saturado. Combine-os com limites, backoff e políticas de resiliência apropriadas; veja resiliência HTTP com Polly.
Trabalho de CPU é uma pergunta diferente
async não acelera cálculo intensivo de CPU. Dividir trabalho em mais tarefas pode aumentar contenção e uso de CPU, principalmente em servidor. Antes de paralelizar, meça duração, uso de CPU e tamanho do trabalho; avalie se o cálculo pode ser reduzido ou se uma fila de trabalho separada atende melhor à necessidade.
Investigue fila do ThreadPool, CPU e latência juntos. O artigo de diagnóstico de performance .NET mostra como observar contadores e traces.
Não crie trabalho em segundo plano sem ciclo de vida
Evite iniciar uma tarefa e descartá-la sem observar exceções ou garantir que os serviços usados continuarão válidos. Em aplicações web, use uma fila e um BackgroundService hospedado quando o trabalho precisa continuar após a resposta, com persistência e estratégia de retry quando necessário. Retornar ao cliente não garante que o processo permaneça ativo.
Valide sob carga representativa
Compare p95/p99, throughput, erros e métricas de dependências antes e depois. Uma rota pode ficar mais rápida para uma chamada e sobrecarregar o banco quando várias ocorrem em paralelo. Use k6 para modelar carga autorizada e o guia de performance em .NET para organizar o ciclo de hipótese e medição.