Módulo 9 - Async e asyncio

Async ou threads: como escolher

10 min de leitura · por Cesar Gargiulo, revisado pela equipe ValorFinal e GuardiaSec · Atualizado em 01/07/2026

O que você vai aprender

  • Comparar async e threads lado a lado.
  • Escolher a ferramenta certa conforme o problema.
  • Entender por que código bloqueante trava o event loop.
  • Fechar o raciocínio de concorrência do curso.

Async e threads lado a lado

Depois de dois módulos, a pergunta natural é: quando usar cada um? Comece pelo que os une. Async e threads atacam o mesmo problema, o I/O bound, aquele em que o programa espera muito por rede, disco ou banco. E os dois compartilham a mesma limitação: rodam efetivamente em um thread de trabalho no que toca ao Python, então nenhum quebra o GIL para acelerar cálculo. Para CPU bound, a resposta dos dois módulos é a mesma, processos. A escolha entre async e threads, portanto, só se coloca dentro do território do I/O, e aí entram as diferenças de escala e de estilo.

Prefira threads quando

  • O código já usa bibliotecas bloqueantes comuns
  • São poucas ou moderadas tarefas concorrentes
  • Você quer paralelizar código existente com pouco esforço
  • A simplicidade de encaixar importa mais que a escala

Prefira async quando

  • Há milhares de conexões concorrentes
  • Você constrói um servidor ou cliente de rede do zero
  • Existem bibliotecas assíncronas para o que você chama
  • Você quer um modelo previsível, sem travas manuais

O perigo de bloquear o loop

Há uma armadilha que derruba quem começa com async: misturar código bloqueante dentro de uma corrotina. O modelo assíncrono só funciona porque cada tarefa cede o controle nos pontos de await. Se você chama, dentro de uma corrotina, uma operação que não cede o controle, como o time.sleep comum, uma leitura síncrona de arquivo ou uma chamada de rede de uma biblioteca bloqueante, o event loop fica preso naquela chamada. Durante todo esse tempo, nenhuma outra corrotina avança, mesmo que a espera delas já tivesse terminado. O async vira um código sequencial disfarçado, e você perde justamente o que foi buscar.

import asyncio
import time

async def certo(nome):
    await asyncio.sleep(1)     # cede o loop: outras corrotinas avancam
    return nome

async def errado(nome):
    time.sleep(1)              # BLOQUEIA o loop: tudo para durante 1s
    return nome

async def main():
    # com 'certo', gather termina em ~1s; com 'errado', em ~3s
    inicio = time.perf_counter()
    await asyncio.gather(certo("A"), certo("B"), certo("C"))
    print(f"certo: {time.perf_counter() - inicio:.1f}s")

asyncio.run(main())

asyncio.sleep cede o loop; time.sleep dentro de async bloqueia tudo e mata a concorrência.

Fechando o raciocínio de concorrência

Junte tudo e o mapa de decisão fica claro. Primeiro pergunte se a tarefa é I/O bound ou CPU bound. Se for CPU bound, cálculo pesado, nem async nem threads ajudam; use processos, com o ProcessPoolExecutor do módulo anterior. Se for I/O bound, você está no território de async e threads. Aí a segunda pergunta é sobre escala e código: para muitas conexões, servidores de rede e projetos novos com bibliotecas assíncronas, prefira async, que escala melhor e tem modelo previsível. Para encaixar concorrência em código já existente, com bibliotecas bloqueantes comuns e um volume moderado de tarefas, threads costumam ser o caminho mais simples.

PerguntaRespostaFerramenta
É cálculo pesado (CPU)?SimProcessos (ProcessPoolExecutor)
É espera de I/O e há muitas conexões?Simasync com asyncio
É I/O em código bloqueante existente?SimThreads (ThreadPoolExecutor)
A operação é rápida e única?SimNenhum: código comum basta

Primeiro I/O ou CPU; depois, escala e estilo do código decidem entre async e threads.

Uma última recomendação, no espírito do curso: meça antes de escolher. Concorrência acrescenta complexidade, e nem todo programa precisa dela. Se uma tarefa roda rápido o bastante em código comum, deixe simples. Quando o volume de I/O crescer e o tempo de espera pesar, aí sim vale trazer async ou threads, guiado por medição, não por palpite. Com este módulo, você fecha a frente de concorrência do curso e segue para a orientação a objetos avançada, onde o foco volta ao projeto das suas próprias estruturas.

Teste rápido

Por que usar time.sleep dentro de uma corrotina é um problema?

Perguntas frequentes

Async é sempre melhor que threads?
Não. Async escala melhor para muitas conexões e tem um modelo previsível, mas exige que as bibliotecas usadas sejam assíncronas e que o código todo respeite os pontos de await. Threads encaixam concorrência em código bloqueante existente com menos esforço. A melhor escolha depende do projeto, do volume e das bibliotecas disponíveis.
Posso misturar async e threads no mesmo programa?
Pode, com cuidado. Um padrão comum é rodar uma função bloqueante fora do event loop, delegando-a a um executor, para não travar o loop. Combinar os dois mundos exige entender a fronteira entre eles; para começar, é mais seguro escolher um modelo por vez até dominar cada um.
Se eu tiver uma biblioteca só bloqueante, dá para usar em async?
Dá, mas não chamando direto na corrotina, o que travaria o loop. O caminho é executá-la fora do loop, em um executor de threads ou processos, e aguardar o resultado. Assim o event loop continua livre para tocar as outras corrotinas enquanto a função bloqueante roda em outro lugar.
E se a tarefa for cálculo pesado, async resolve?
Não. Async roda em um único thread; um cálculo pesado dentro de uma corrotina prende o event loop e trava tudo. Para CPU bound, a resposta é a mesma dos dois módulos: paralelismo com processos, via ProcessPoolExecutor. Async e threads são para espera de I/O, não para conta.
Por onde começo se nunca usei concorrência?
Comece medindo se você realmente precisa. Se o programa já é rápido, mantenha simples. Se há muita espera de I/O, o ThreadPoolExecutor costuma ser o passo mais fácil para um primeiro ganho. Quando o volume de conexões crescer muito, aí o async passa a compensar o esforço extra de reescrever no modelo assíncrono.

Fontes

Seu progresso fica salvo neste aparelho. Assinantes sincronizam entre os aparelhos.