1. Introdução
Imagine que seu código é uma fila no mercado. Se você executa um método síncrono, toda a fila (seu fluxo) espera enquanto o caixa atende um cliente antes de começar o próximo. Se o método for assíncrono, você simplesmente deixa suas compras no caixa e sai fazer outras coisas; quando o caixa terminar — ele te chama.
Um método assíncrono (método async) no .NET é um método normal com uma "etiqueta" especial na frente (async) que permite usar o operador await dentro para esperar operações assíncronas (por exemplo, rede ou arquivos) de forma não bloqueante, sem travar a thread principal. Essa "etiqueta" transforma o mercado síncrono chato em um atendimento moderno sem filas.
Assinatura básica de um método assíncrono
Um método assíncrono é declarado com o modificador async na assinatura. Aqui vai o exemplo mais básico:
public async Task MyAsyncMethod()
{
// Seu código assíncrono aqui
}
Tipos de retorno de métodos assíncronos:
| Tipo de retorno | Descrição | Exemplo |
|---|---|---|
|
O método faz trabalho de forma assíncrona, mas não retorna resultado | |
|
O método faz trabalho assíncrono e retorna um resultado do tipo T | |
|
Usado só para eventos. Não recomendado no restante do código! | |
|
Para cenários de alta performance, quando o resultado frequentemente está pronto de forma síncrona | — |
Importante: Não use async void exceto em handlers de eventos! Caso contrário você corre o risco de ficar sem tratamento adequado de erros.
2. Exemplo simples de método assíncrono
Voltando ao nosso app de treino (pode ser um programa de console simples), vamos adicionar um método assíncrono que obtém dados da rede. Vamos emular um atraso.
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("Iniciando método assíncrono...");
await MyFakeDownloadAsync();
Console.WriteLine("Operação assíncrona concluída!");
}
static async Task MyFakeDownloadAsync()
{
Console.WriteLine("Começando o download (emulando atraso de 2 seg)...");
await Task.Delay(2000); // Emulando operação longa
Console.WriteLine("Download concluído!");
}
}
Comentários sobre o exemplo:
- A palavra-chave async está em Main e em MyFakeDownloadAsync.
- O operador await só pode ser usado dentro de métodos marcados com async.
- O método Task.Delay(2000) emula uma operação demorada (por exemplo, uma requisição de rede).
- Depois que aparece o await, a thread não é bloqueada — a execução do Main "pausa" até o término do delay.
3. Como funciona um método assíncrono por baixo do capô?
Quando o compilador vê um método com a palavra-chave async, ele o transforma em uma "máquina de estados" que lembra o ponto de parada, as variáveis locais e continua a execução depois que a operação awaited terminar.
Aqui está um esquema bem simplificado:
+-----------------------------------------------------------+
| Chamada do método assíncrono (por exemplo, await X) |
+-------------------------+---------------------------------+
|
v
Operação concluída? (não Task.Completed)
| |
| sim | não
v v
Pausar execução, Retornar
lembrar o contexto imediatamente
(ponto, variáveis) (ou lançar erro)
|
A operação assíncrona termina...
|
v
A máquina de estados "acorda" o método,
execução continua a partir do ponto após await
Não é necessário decorar cada detalhe: o contexto (ponto de parada e variáveis locais) é salvo e restaurado automaticamente quando a operação terminar.
Onde é possível (e onde não) usar as palavras-chave async e await
- async é colocado antes da declaração do método (ou da lambda).
- Dentro de um método marcado com async você pode (e deve) usar await.
- Se você tentar usar await em um método sem async — vai dar erro de compilação.
- Se declarar async mas não tiver nenhum await no método — receberá um aviso; o método vai executar de forma síncrona e retornar uma Task já concluída.
4. Variações de valores de retorno e suas particularidades
Retorno Task
Se o método faz ações assíncronas mas não retorna valor, use Task:
public async Task SaveToFileAsync(string path)
{
await Task.Delay(1000);
// Salvamos algo no arquivo
}
Retorno Task<T>
Se precisar de um resultado, use Task<T>:
public async Task<int> CalculateAsync()
{
await Task.Delay(500);
return 42;
}
Chamada:
int result = await CalculateAsync();
Retorno async void
Use apenas para eventos! Por exemplo, um handler de clique:
private async void Button_Click(object sender, EventArgs e)
{
await Task.Delay(1000);
// Fazemos mais alguma coisa
}
Por que não usar async void em métodos normais? Porque você não vai saber quando esse método termina e não conseguirá capturar exceções corretamente dentro dele.
Retornos ValueTask e ValueTask<T>
Surgiram para bibliotecas de alta performance, quando o resultado frequentemente já está disponível de forma síncrona — evitam alocações desnecessárias de Task. No começo da carreira você pode não usar: não aparecem com tanta frequência.
5. Exemplo: calculadora assíncrona que retorna resultado
Vamos supor que o programa calcule a soma de dois números "com atraso", como se fosse uma operação muito complexa:
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("Digite o primeiro número:");
int a = int.Parse(Console.ReadLine()!);
Console.WriteLine("Digite o segundo número:");
int b = int.Parse(Console.ReadLine()!);
Console.WriteLine("Executando cálculo...");
int sum = await CalculateSumAsync(a, b);
Console.WriteLine($"Soma: {sum}");
}
static async Task<int> CalculateSumAsync(int x, int y)
{
await Task.Delay(2000); // Operação "lenta"
return x + y;
}
}
O que importa aqui:
- CalculateSumAsync é um método assíncrono que retorna Task<int> e contém um await.
- A chamada em Main usa await (lembrete: desde o C# 7.1 o método Main pode ser assíncrono).
- Enquanto a soma é calculada, a thread não fica bloqueada — você pode mostrar progresso ou fazer trabalho paralelo.
6. Métodos aninhados e awaits encadeados
Um método assíncrono pode chamar outro método assíncrono, que por sua vez chama outro. Toda a cadeia continua automaticamente:
public async Task<int> StartWorkAsync()
{
int data = await GetDataAsync();
int result = await ProcessDataAsync(data);
return result;
}
É simples: espera o fim de uma operação, depois da outra.
7. Armadilhas e erros comuns
1. Não esqueça do async!
Se você esquecer async, o compilador vai reclamar se encontrar await dentro do método e não vai deixar compilar.
2. Não use async void em métodos normais
Métodos async void (exceto handlers de eventos) são "buracos negros" para exceções. Erros dentro deles podem "afundar" e você nem vai saber.
3. Método assíncrono SEM await
Isso é possível, mas sem sentido — o método vai executar de forma síncrona e retornar uma Task já completada.
public async Task DoNothingAsync()
{
// Ops, nenhum await!
}
4. Retornar Task de um método NÃO assíncrono
Isso é feito para unificar interfaces: algumas operações são realmente assíncronas, outras são instantâneas.
public Task<int> Foo()
{
// return 42; // Assim não dá!
return Task.FromResult(42); // Dá pra fazer, mas não há assíncronia aqui.
}
GO TO FULL VERSION