C#: Programação assíncrona

1. Síncrono x Assíncrono

O código síncrono é executado linha por linha, em ordem. Quando encontra uma operação demorada, ele bloqueia a thread atual, fazendo com que o programa pare de responder. O código assíncrono libera a thread enquanto aguarda operações demoradas, permitindo que o programa continue realizando outras tarefas.

Problemas com o bloqueio síncrono:

Vantagens da comunicação assíncrona:

▶ Exemplo

CSHARP
using System;
using System.Threading;

Console.WriteLine("Sync start: " + DateTime.Now.ToString("HH:mm:ss"));
Thread.Sleep(2000);
Console.WriteLine("Sync end: " + DateTime.Now.ToString("HH:mm:ss"));
▶ Experimente
TEXT 📖 Somente leitura
Sync start: 10:00:00
Sync end: 10:00:02

2. Tarefa e Tarefa<T>

Task representa uma operação assíncrona que não retorna um resultado. Task<T> representa uma operação assíncrona que retorna um resultado do tipo T. Esses são os tipos fundamentais da programação assíncrona em C#.

Tipo Descrição Exemplo
Task Operação assíncrona sem valor de retorno Task.Delay(1000)
Task<T> Operação assíncrona com valor de retorno Task.FromResult(42)

▶ Exemplo

CSHARP
using System;
using System.Threading.Tasks;

Task noResultTask = Task.Delay(500);
Task```<int>``` resultTask = Task.FromResult(42);

await noResultTask;
int value = await resultTask;
Console.WriteLine("Retrieved value: " + value);
▶ Experimente
TEXT 📖 Somente leitura
Retrieved value: 42

3. Sintaxe de async/await

O modificador async marca um método como assíncrono, e o operador await aguarda a conclusão de uma operação assíncrona sem bloquear a thread. O await só pode ser usado dentro de um método async.

Regras de sintaxe:

▶ Exemplo

CSHARP
using System;
using System.Threading.Tasks;

async Task DoWorkAsync()
{
    Console.WriteLine("Work start: " + DateTime.Now.ToString("HH:mm:ss.fff"));
    await Task.Delay(1000);
    Console.WriteLine("Work end: " + DateTime.Now.ToString("HH:mm:ss.fff"));
}

async Task```<string>``` GetMessageAsync()
{
    await Task.Delay(500);
    return "Async message arrived";
}

await DoWorkAsync();
string msg = await GetMessageAsync();
Console.WriteLine(msg);
▶ Experimente
TEXT 📖 Somente leitura
Work start: 10:00:00.000
Work end: 10:00:01.000
Async message arrived

4. Task.Run

Task.Run transfere tarefas que exigem muito da CPU para o pool de threads para execução, evitando o bloqueio da thread atual. Adequado para operações que exigem muito processamento, mas não para operações de E/S.

▶ Exemplo

CSHARP
using System;
using System.Threading.Tasks;

int ComputeHeavy()
{
    int result = 0;
    for (int i = 0; i < 100_000_000; i++)
    {
        result += i;
    }
    return result;
}

Console.WriteLine("Starting computation...");
int total = await Task.Run(() => ComputeHeavy());
Console.WriteLine("Computation result: " + total);
▶ Experimente
TEXT 📖 Somente leitura
Starting computation...
Computation result: 4999999950000000

5. Task.WhenAll / WhenAny / Delay

Método Descrição
Task.WhenAll Aguarda a conclusão de todas as tarefas
Task.WhenAny Aguarda a conclusão de qualquer uma das tarefas
Task.Delay Aguarda de forma assíncrona pelo número especificado de milissegundos, substituindo Thread.Sleep

▶ Exemplo

CSHARP
using System;
using System.Threading.Tasks;

async Task```<string>``` FetchAsync(string site)
{
    await Task.Delay(new Random().Next(500, 1500));
    return site + " loaded";
}

Task```<string>``` t1 = FetchAsync("SiteA");
Task```<string>``` t2 = FetchAsync("SiteB");
Task```<string>``` t3 = FetchAsync("SiteC");

string[] all = await Task.WhenAll(t1, t2, t3);
Console.WriteLine("--- WhenAll complete ---");
foreach (string s in all)
{
    Console.WriteLine(s);
}

Task```<string>``` first = await Task.WhenAny(t1, t2, t3);
Console.WriteLine("First completed: " + first.Result);
▶ Experimente
TEXT 📖 Somente leitura
--- WhenAll complete ---
SiteA loaded
SiteB loaded
SiteC loaded
First completed: SiteA loaded

6. Convenção de nomenclatura para métodos assíncronos

Os nomes dos métodos assíncronos devem usar o sufixo Async. Essa é uma convenção do .NET que facilita a distinção entre métodos síncronos e assíncronos.

Regras:

▶ Exemplo

CSHARP
using System;
using System.Threading.Tasks;

async Task```<string>``` ReadFileAsync(string path)
{
    await Task.Delay(200);
    return "Read: " + path;
}

async Task SaveDataAsync(string data)
{
    await Task.Delay(100);
    Console.WriteLine("Saved: " + data);
}

string content = await ReadFileAsync("/data.txt");
await SaveDataAsync(content);
▶ Experimente
TEXT 📖 Somente leitura
Saved: Read: /data.txt

7. Mecanismo de cancelamento do CancellationToken

CancellationToken é utilizado para o cancelamento cooperativo de operações assíncronas. O chamador envia um sinal de cancelamento por meio de CancellationTokenSource, e o chamado verifica o token e encerra a operação antecipadamente.

Instruções de uso:

  1. Criar um CancellationTokenSource
  2. Passe cts.Token para o método assíncrono
  3. O método assíncrono verifica periodicamente token.IsCancellationRequested ou o passa para APIs canceláveis
  4. Ligue para cts.Cancel() caso seja necessário cancelar

▶ Exemplo

CSHARP
using System;
using System.Threading;
using System.Threading.Tasks;

async Task LongWorkAsync(CancellationToken token)
{
    for (int i = 0; i < 10; i++)
    {
        token.ThrowIfCancellationRequested();
        Console.WriteLine("Step " + (i + 1));
        await Task.Delay(300, token);
    }
    Console.WriteLine("All done");
}

using var cts = new CancellationTokenSource();
cts.CancelAfter(1200);

try
{
    await LongWorkAsync(cts.Token);
}
catch (OperationCanceledException)
{
    Console.WriteLine("Operation cancelled");
}
▶ Experimente
TEXT 📖 Somente leitura
Step 1
Step 2
Step 3
Step 4
Operation cancelled

8. IAsyncEnumerable<T> Fluxos assíncronos

O C# 8 introduziu o IAsyncEnumerable<T>, que oferece suporte à geração e ao consumo assíncronos de fluxos de dados. Use o await foreach para iterar itens de forma assíncrona, o que é adequado para cenários como leituras em lote de bancos de dados e respostas de rede em streaming.

▶ Exemplo

CSHARP
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

async IAsyncEnumerable```<int>``` GenerateNumbersAsync()
{
    for (int i = 1; i <= 5; i++)
    {
        await Task.Delay(200);
        yield return i;
    }
}

await foreach (int num in GenerateNumbersAsync())
{
    Console.WriteLine("Received: " + num);
}
▶ Experimente
TEXT 📖 Somente leitura
Received: 1
Received: 2
Received: 3
Received: 4
Received: 5

9. Armadilhas comuns na programação assíncrona

(1) ⚠️ Evite funções assíncronas com retorno void

async void não pode ser awaitado, as exceções não podem ser capturadas e isso pode causar falhas no aplicativo. Utilize-o apenas em manipuladores de eventos.

(2) ⚠️ Evite usar .Result e .Wait()

Chamar .Result ou .Wait() em um contexto de interface de usuário (UI) ou ASP.NET pode causar um impasse: o método assíncrono aguarda que o chamador libere a thread, enquanto o chamador aguarda que o método assíncrono seja concluído — ambos os lados ficam esperando um pelo outro.

(3) ⚠️ ConfigureAwait(false)

O código da biblioteca deve utilizar ConfigureAwait(false) para evitar a captura do contexto de sincronização, prevenindo impasses e melhorando o desempenho.

▶ Exemplo

CSHARP
using System;
using System.Threading.Tasks;

async Task```<string>``` BadMethodAsync()
{
    await Task.Delay(100);
    return "Done";
}

async Task SafeUsageAsync()
{
    string result = await BadMethodAsync();
    Console.WriteLine("Correct usage: " + result);
}

async Task LibraryMethodAsync()
{
    string result = await BadMethodAsync().ConfigureAwait(false);
    Console.WriteLine("Library usage: " + result);
}

await SafeUsageAsync();
await LibraryMethodAsync();
▶ Experimente
TEXT 📖 Somente leitura
Correct usage: Done
Library usage: Done

❓ Perguntas Frequentes

P: Qual é a diferença entre async void e async Task? R: async void não pode ser aguardado e as exceções não podem ser capturadas; deve ser usado apenas para manipuladores de eventos. async Task é a abordagem padrão — pode ser aguardado e as exceções podem ser tratadas.

P: Quando devo usar o Task.Run? R: Apenas para tarefas computacionais que exigem muito da CPU. As operações de E/S devem usar APIs assíncronas nativas (como ReadAsync), sem serem envolvidas pelo Task.Run.

P: Que problemas o .Result pode causar? R: Em contextos de sincronização UI/ASP.NET, pode ocorrer um impasse — a thread chamadora aguarda a conclusão da Task, enquanto a Task aguarda a liberação do contexto de sincronização, causando um bloqueio mútuo.

P: Quando devo usar ConfigureAwait(false)? R: Use-o no código de bibliotecas para evitar a captura do contexto de sincronização, prevenindo impasses e melhorando o desempenho. O código da camada de aplicação da interface do usuário (UI) e do ASP.NET geralmente não precisa disso.

📖 Resumo

📝 Exercícios

  1. Escreva um método assíncrono DownloadAllAsync que utilize Task.WhenAll para baixar três fontes de dados simuladas simultaneamente e retorne uma matriz com todos os resultados
  2. Escreva um método de contagem regressiva assíncrona que suporte CancellationToken, exiba os segundos restantes a cada segundo e imprima “Contagem regressiva cancelada” quando for cancelada
  3. Use IAsyncEnumerable<int> para gerar os primeiros 20 números de Fibonacci, repita o processo com await foreach e exiba cada um deles
  4. Identifique o problema no código a seguir e corrija-o: async void Button_Click() { var data = GetDataAsync().Result; Console.WriteLine(data); }
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%