CodeGym /Cursos /C# SELF /Assincronicidade vs. Multithreading ( async/await e Thre...

Assincronicidade vs. Multithreading ( async/await e Thread)

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

1. Introdução

Em programação, iniciantes (e até desenvolvedores experientes) frequentemente confundem duas conceitos parecidos, mas na prática diferentes: multithreading e assincronicidade. Em entrevistas essa pergunta é comum pra checar se a pessoa entende a diferença, já que disso depende diretamente como escrever código rápido e responsivo.

Vamos entender onde está a pegadinha.

Multithreading: quando muitas mãos trabalham

Multithreading é a organização do trabalho do programa usando múltiplas threads. Uma thread é um fio de execução, uma "pista" separada pela qual o processador executa instruções do programa. Um processo (por exemplo, nossa aplicação .NET) pode iniciar várias threads pra diferentes tarefas serem executadas ao mesmo tempo.

Exemplo do dia a dia: Você é gerente de projeto que delega tarefas a diferentes colegas, e todas essas tarefas são feitas simultaneamente. Por exemplo, um escreve o relatório, outro liga para o cliente, o terceiro prepara a apresentação.

Ideia chave do multithreading: as tarefas acontecem realmente em paralelo (ou quase-paralelo, se o processador for um só, graças ao rápido contexto de troca).

Assincronicidade: saber não ficar parado

Assincronicidade é organizar o código de forma que o programa possa fazer outra coisa enquanto espera a conclusão de uma operação demorada (por exemplo, resposta da Internet ou leitura de arquivo). Código assíncrono não precisa necessariamente usar múltiplas threads! Ele simplesmente não bloqueia a execução do programa enquanto espera por uma operação.

Exemplo do dia a dia: Em vez de ficar parado na frente da cafeteira vendo o café pingar, você manda a cafeteira fazer o café e faz outra coisa (responde e-mails, lê notícias), pegando o café quando estiver pronto.

Ideia chave da assincronicidade: não ficar ocioso enquanto espera por tarefas longas, e fazer algo útil nesse tempo.

2. Qual é a diferença?

Muitas vezes assincronicidade e multithreading são usadas juntas, e isso só aumenta a confusão. Mas na real essas técnicas respondem perguntas diferentes:

  • Multithreading é pra realmente paralelizar trabalho usando CPUs/cores disponíveis.
  • Assincronicidade é pra organizar inteligentemente a espera por recursos (I/O de rede, I/O de disco etc.), sem bloquear thread.

Dá pra combinar as duas, mas elas não são obrigatoriamente ligadas.

Visualização rápida

Multithreading (Threads) Assincronicidade (Async)
Quando aplicar? Quando a tarefa consome CPU (CPU-bound: cálculos, renderização, processamento de arrays) Quando a tarefa está esperando por algo (I/O-bound: rede, disco, banco de dados)
O que o código faz? Usa o processador, inicia várias threads, paraleliza de fato Quando espera dados (fica "ocioso"), libera a thread — ela pode fazer outra coisa
O que gerencia? Quantidade de threads executando simultaneamente Quem está ocupado/livre e o que fazer quando a operação terminar
Exemplo típico Compressão de vídeo, renderização de imagem Download de arquivo, requisição HTTP ao servidor

Exemplo 1: Multithreading — contamos rápido

Suponha que temos uma tarefa pesada: somar números grandes.


void ComputeSum(long start, long end)
{
    long sum = 0;
    for (long i = start; i <= end; i++)
    {
        sum += i;
    }
    Console.WriteLine($"Soma de {start} até {end} = {sum}");
}

// Iniciamos três tarefas ao mesmo tempo — cada uma calcula sua parte
Thread t1 = new Thread(() => ComputeSum(1, 1000_000_000));
Thread t2 = new Thread(() => ComputeSum(1000_000_001, 2000_000_000));
Thread t3 = new Thread(() => ComputeSum(2000_000_001, 3000_000_000));

t1.Start();
t2.Start();
t3.Start();

// Esperamos todas as threads terminarem
t1.Join();
t2.Join();
t3.Join();

Por que usar threads aqui?
Porque o trabalho é CPU-bound. Threads realmente carregam o processador, e se você tem uma máquina multi-core — o trabalho fica mais rápido.

Exemplo 2: Assincronicidade — esperamos resposta do servidor

Rede é lenta, e enquanto esperamos a resposta do servidor, a thread pode ficar "livre".


// Baixa a página assincronamente, a thread não fica bloqueada
async Task DownloadPageAsync()
{
    using HttpClient client = new HttpClient();
    string html = await client.GetStringAsync("https://dotnet.microsoft.com/");
    Console.WriteLine(html.Length);
}

Por que usar assincronicidade aqui?
Damos ao sistema o comando "comece o download" e não bloqueamos a thread; esperamos a notificação quando os dados chegarem.

3. Assincronicidade sem multithreading: mito ou verdade?

Pergunta: Todo código assíncrono sempre roda em uma nova thread?
Resposta: Não! Muitas vezes assincronicidade não requer threads adicionais.

Quando você, por exemplo, faz await file.ReadAsync(...), o .NET inicia a operação assincronamente no nível do SO, e a thread que chamou esse método logo fica "livre" e volta para o thread pool. Quando a operação terminar, algum thread livre do pool continuará a execução da sua tarefa.

  • Se em vez de assincronicidade você usar a versão síncrona (file.Read(...)) — a thread simplesmente esperaria a conclusão da operação sem fazer nada.
  • Código assíncrono diz: "Processador, enquanto esperamos — faz outra coisa!"

Ilustração importante:


// A "thread" não fica bloqueada, só esperamos a operação completar
await Task.Delay(1000); // Só esperar um segundo — não ocupa CPU!

Multithreading sem assincronicidade

Às vezes paralelizar trabalho faz sentido apenas com threads: cálculos pesados, grandes loops de processamento de dados etc. Nesse caso assincronicidade não ajuda a acelerar os próprios cálculos, já que o processador já estará a 100%.

Clássico: processando arquivos grandes


// Esse código realmente usa o processador — assincronicidade não vai ajudar.
void CalculateHash(string file)
{
    byte[] data = File.ReadAllBytes(file); // síncrono!
    // Calcula o hash...
}

Quer acelerar — rode várias threads, cada uma trabalhando em seu arquivo.

4. Como isso aparece na sua aplicação?

Operações assíncronas (await)

No nosso app de estudo dá pra adicionar carregamento assíncrono de dados. Por exemplo, quando você pede taxas de câmbio ou clima — é melhor fazer isso assincronamente.


async Task GetWeatherAsync(string city)
{
    using HttpClient client = new HttpClient();
    string json = await client.GetStringAsync($"https://api.weather.com/{city}");
    // Continuamos quando a resposta chegar
    Console.WriteLine($"Clima em {city}: {json}");
}

O que acontece por baixo?

A chamada await "corta" seu método em duas partes:

  • Chamou a operação assíncrona — a thread do SO fica "liberada" e pode fazer outras tarefas.
  • Quando os dados chegam — a execução do método continua em algum thread livre do pool.

Multithreading para cálculos pesados

No nosso exemplo do calculador (suponha que ele passou a processar grandes arrays) — faz sentido rodar cálculos em threads separadas.


// Dividimos a tarefa grande em partes menores, cada uma calcula sua parte
List<Thread> threads = new List<Thread>();
for (int i = 0; i < 4; i++)
{
    int rangeStart = i * 1000000;
    int rangeEnd = (i + 1) * 1000000 - 1;
    Thread t = new Thread(() => ComputeSum(rangeStart, rangeEnd));
    threads.Add(t);
    t.Start();
}
// Esperamos todas as threads terminarem
foreach (Thread t in threads) t.Join();

5. Erros típicos e nuances ao trabalhar com assincronicidade

Erro nº1: usar async "pra velocidade".
Muita gente pensa que async acelera a execução do código. Isso não é verdade. Assincronicidade é sobre responsividade, não sobre velocidade.

Se a tarefa é CPU-bound — assincronicidade não vai torná-la mais rápida.
Se a tarefa é I/O-bound (rede, disco) — assincronicidade é útil porque a thread não fica ociosa e pode fazer outro trabalho.

Erro nº2: bloquear threads com Wait() e Result.
Em código assíncrono é proibido chamar Wait() ou acessar Result de tasks. Isso quase sempre leva a bloqueios de threads e deadlocks.


// Ruim! Vai bloquear a thread e causar problemas
var result = GetDataAsync().Result;

async Task<string> GetDataAsync() { /* ... */ return "dados"; }

O jeito certo é usar await e não bloquear a thread.

Erro nº3: assincronicidade e UI.
Em apps gráficos (WPF, WinForms) o problema principal é não congelar a UI-thread. Se no thread principal você rodar uma operação longa ou bloqueante, a aplicação "trava". Assincronicidade resolve isso: trabalho pesado roda em background e a interface continua responsiva.

Erro nº4: falta de convenção de nomes para métodos assíncronos.
Se você não adicionar o sufixo Async em métodos assíncronos, fica fácil confundir o que é síncrono e o que é assíncrono. Isso leva a bloqueios acidentais e bugs ao chamar métodos. Sempre nomeie métodos assíncronos com Async no fim.

2
Tarefa
C# SELF, nível 59, lição 1
Bloqueado
Exemplo de uma operação assíncrona simples
Exemplo de uma operação assíncrona simples
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION