CodeGym /Cursos /C# SELF /Palavras-chave async ...

Palavras-chave async e await

C# SELF
Nível 59 , Lição 2
Disponível

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.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION