1. Introdução
Antes de pular no código, vamos entender a motivação e imaginar uma situação típica. Suponha que seu programa baixa um arquivo da internet:
// Pseudocódigo
var data = DownloadFile("https://example.com/file");
ProcessData(data);
O problema aqui é o seguinte: enquanto o download acontece, o programa "congela". Nenhuma outra ação é executada — o usuário não pode mover o mouse, clicar em botões, nem mesmo esperar sem uma interface desagradavelmente "travada".
Antes (e em outras linguagens) para resolver esse problema era preciso usar threads (Thread), tarefas (Task), delegates, timers — tudo isso levava a um acúmulo de código difícil de ler e manter. Em C# a equipe decidiu facilitar a vida: surgiu a assincronicidade com as palavras-chave async e await. Agora dá pra escrever código assíncrono quase tão simples quanto o normal.
Dor clássica "assíncrona" sem async/await
Para contraste, vamos ver como seria executar uma operação longa usando threads, se quiséssemos não bloquear a interface:
// Exemplo sem async/await, manualmente
var thread = new Thread(() =>
{
var data = DownloadFile("https://example.com/file");
Console.WriteLine("Arquivo baixado!");
});
thread.Start();
Essa abordagem é meio tosca: você precisa gerenciar threads manualmente, não há jeito simples de "esperar" o resultado, e tratar erros é complicado.
2. Assincronicidade em C#: sintaxe
Definição: o que são async e await?
async — é um modificador que impede um método de ser chato e o torna assíncrono. Esse método normalmente retorna Task (ou Task<T>), ou ValueTask. É uma promessa de que o resultado virá, só que um pouco depois (como um pedido numa loja online — você comprou e espera).
await — é um operador que diz: "Pare nesta linha quando chegar aqui. Espere até a operação terminar, mas não bloqueie a thread! O resto do programa pode continuar fazendo outras coisas".
Como fica um método assíncrono?
public async Task MyAsyncMethod()
{
Console.WriteLine("Carregando arquivo...");
var data = await DownloadFileAsync("https://example.com/file"); // Esperamos o resultado de forma assíncrona!
Console.WriteLine("Pronto!");
}
Repare:
- O método recebeu o modificador async.
- Dentro do método usamos await para a operação assíncrona.
- O método retorna Task (ou Task<T>, se houver valor de retorno).
Visualização: o que acontece ao chamar um método async?
graph LR
A[Chamada MyAsyncMethod] --> B[Execução até o await]
B --> C[Chamada DownloadFileAsync]
C --> D{Espera}
D --> |Arquivo não baixado| E[Liberação da thread]
D --> |Arquivo baixado| F[Execução após o await]
F --> G[Término do método]
- Antes do primeiro await o método executa de forma síncrona.
- No await a execução do método é suspensa, o controle retorna para o chamador.
- Quando a operação assíncrona termina, a execução continua após o await — como se nada tivesse acontecido.
3. Exemplo: download assíncrono usando async/await
Vamos supor que nosso app de estudo baixa um texto da internet e imprime seu tamanho, sem bloquear o resto do código.
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
// Método assíncrono (retorna Task)
public static async Task DownloadAndPrintLengthAsync(string url)
{
Console.WriteLine("Começando o download...");
// Usamos HttpClient — ele tem métodos assíncronos
using (var client = new HttpClient())
{
string data = await client.GetStringAsync(url);
Console.WriteLine($"Download concluído! Tamanho do texto: {data.Length} caracteres.");
}
Console.WriteLine("Método finalizado.");
}
static void Main()
{
// Iniciamos a operação assíncrona e aguardamos sua conclusão
var task = DownloadAndPrintLengthAsync("https://www.example.com");
task.Wait(); // Para exemplos simples de console isso é aceitável. Em apps UI ou web reais essa chamada vai bloquear e pode causar deadlocks.
}
}
Explicações:
- DownloadAndPrintLengthAsync — totalmente assíncrono, graças a async e await.
- Dentro do método esperamos a conclusão do download assíncrono da string usando await.
- No Main() iniciamos a tarefa e explicitamente aguardamos com Wait(). Em C# moderno você pode tornar o próprio Main assíncrono (async Task Main) e usar await direto.
4. Como isso funciona?
Diferença entre código síncrono e assíncrono
VARIANTE SÍNCRONA
Console.WriteLine("Início");
string data = client.GetStringAsync(url).Result; // .Result bloqueia a thread!
Console.WriteLine("Operação concluída");
VARIANTE ASSÍNCRONA
Console.WriteLine("Início");
string data = await client.GetStringAsync(url); // A thread não é bloqueada
Console.WriteLine("Operação concluída");
Como o await funciona "por baixo dos panos"?
Quando você usa await, o C# automaticamente "quebra" seu método em duas (ou mais) partes: tudo que veio antes do await e tudo que vem depois. Quando o método assíncrono chamado retorna um Task, seu método retorna para o chamador, e quando o Task termina — a execução continua após o await. Tudo isso acontece automaticamente; você não precisa se preocupar com threads e trocas manualmente.
Curiosidade: o C# "transforma" seu método assíncrono em uma máquina de estados, onde cada "await" é um novo "ponto de retorno".
Uso de async/await em apps reais
static async Task Main(string[] args)
{
var downloadTask = DownloadAndPrintLengthAsync("https://www.example.com");
// Enquanto o download acontece, fazemos outra coisa
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"Trabalhando... iteração {i}");
await Task.Delay(500); // Pausa de 0,5s, simulando trabalho
}
await downloadTask; // Esperamos o término do download
}
Agora o programa baixa algo e não trava: multitarefa "viva", quase como um gato que dorme e observa a tigela ao mesmo tempo.
Assincronicidade ≠ multithreading
Uma diferença importante que iniciantes confundem: código assíncrono pode rodar na mesma thread! Assincronicidade é sobre não bloquear a thread, não sobre criar novas necessariamente. Threads do SO são um recurso caro! Assincronicidade permite "liberar" a thread para continuar trabalhando sem esperar por operações longas (de rede, disco, timers etc.).
Tabela de comparação: quando escolher o quê?
| Cenário | Thread/Task | async/await |
|---|---|---|
| Tasks CPU-bound | Sim | Sim (via Task.Run) |
| Tarefas I/O-bound | Ineficiente | Ideal |
| Muito paralelismo | Complicado | Fácil |
| Simplicidade do código | Hardcore | Fácil de ler |
5. Nuances úteis
Main assíncrono
Com versões modernas do C# você pode tornar o método Main assíncrono!
static async Task Main(string[] args)
{
// Todo seu código assíncrono pode ser awaited direto no Main
await DownloadAndAnalyzeFileAsync("https://example.com/file");
}
Erro típico: esqueceu o await — a task "vazou"
SomeAsyncFunction(); // não damos await, ninguém espera o término!
Como resultado essa tarefa vai rodar "à deriva" — e se ocorrer uma exceção você nem vai saber!
Quando NÃO escrever métodos async
- Se dentro do método não há nenhuma operação assíncrona (nenhum await), não marque ele com async.
- Evite métodos com async void (exceto se você for um event handler).
Regras rápidas e perguntas frequentes
- Posso usar await fora de um método async? Não! Sempre só dentro de métodos marcados como async.
- Posso ter vários await num mesmo método? Sim, pode haver quantos forem necessários — cada um é um "ponto de espera".
- E se eu quiser retornar um resultado? Use Task<T> e escreva return.
- Posso combinar várias tarefas assíncronas? Claro! Dá pra iniciar várias tarefas paralelamente e aguardá-las todas com Task.WhenAll.
6. Erros típicos ao usar async/await
Erro №1: "bola de fogo" — chamando sem await.
DownloadAndPrintLengthAsync("https://www.example.com");
Console.WriteLine("Tudo concluído!"); // Na verdade o download ainda está em andamento!
O código NÃO vai esperar a operação assíncrona. Todas as tarefas assíncronas devem ser ou await-adas ou explicitamente aguardadas com Wait() (mas essa segunda opção é perigosa e pode causar bloqueios).
Erro №2: misturar código síncrono e assíncrono usando .Result ou .Wait().
Esse é um antipadrão que anula todas as vantagens da assincronicidade. Em apps UI ou ASP.NET isso quase certamente vai levar a deadlocks: a tarefa assíncrona espera a liberação da thread, enquanto a thread está bloqueada esperando a tarefa. Lembre-se da mantra: "async all the way" (assincronicidade até o topo).
Erro №3: Uso de async void.
Métodos async void não podem ser aguardados (await), e exceções lançadas neles não são capturáveis por try-catch padrão e geralmente causam encerramento do app. O único uso aceitável de async void é em event handlers (por exemplo, async void Button_Click(...)), quando a assinatura exige. Nos demais casos use async Task.
Erro №4: Async desnecessário em método sem await.
Se você marca um método como async mas não usa await dentro dele, o compilador vai emitir um aviso. Esse código vai rodar totalmente de forma síncrona, mas com custo adicional de criar uma "máquina de estados" desnecessária. Isso confunde e reduz performance.
GO TO FULL VERSION