Async/await em Python: quando e por que usar
Concorrência em Python: como async/await funciona, quando usar e armadilhas comuns
Async/await resolve um problema específico: o que fazer enquanto seu programa espera. Espera por uma resposta HTTP, por um arquivo do disco, por uma consulta ao banco. Sem async, o Python trava e não faz mais nada nesse tempo. Com async, ele pode atender outras requisições enquanto espera.
O problema sem async
import requests
import time
def buscar(url):
return requests.get(url).json()
inicio = time.time()
r1 = buscar("https://api.github.com/users/torvalds")
r2 = buscar("https://api.github.com/users/gvanrossum")
r3 = buscar("https://api.github.com/users/kennethreitz")
print(f"Tempo: {time.time() - inicio:.1f}s") # ~3s (sequencial)
Com async: requisições simultâneas
import asyncio
import httpx # versão async do requests: pip install httpx
import time
async def buscar(cliente, url):
resposta = await cliente.get(url)
return resposta.json()
async def main():
async with httpx.AsyncClient() as cliente:
tarefas = [
buscar(cliente, "https://api.github.com/users/torvalds"),
buscar(cliente, "https://api.github.com/users/gvanrossum"),
buscar(cliente, "https://api.github.com/users/kennethreitz"),
]
resultados = await asyncio.gather(*tarefas)
for r in resultados:
print(r["login"], r["public_repos"])
inicio = time.time()
asyncio.run(main())
print(f"Tempo: {time.time() - inicio:.1f}s") # ~1s (paralelo)
Como funciona o event loop
O Python não cria threads novas. Existe um único event loop — um gerenciador de tarefas que fica verificando quais corrotinas estão prontas para continuar. Quando uma corrotina chega num await, ela "devolve o controle" para o loop, que executa outra enquanto a primeira espera.
Async não é paralelismo — é concorrência. A CPU ainda executa uma coisa por vez, mas o tempo de espera vira tempo útil.
Armadilhas comuns
1. Misturar sync e async
# ERRADO: bloqueia o event loop
async def processar():
import time
time.sleep(5) # trava tudo durante 5s
# CERTO: usa a versão async
async def processar():
await asyncio.sleep(5) # só essa corrotina espera
2. Esquecer o await
async def buscar(url):
resposta = httpx.AsyncClient().get(url) # ← faltou await
# resposta é uma corrotina, não o resultado
3. CPU-bound não se beneficia de async
# Async serve para I/O (rede, disco, banco).
# Para cálculos pesados (ML, processamento de imagem), use multiprocessing.
import multiprocessing
with multiprocessing.Pool() as pool:
resultados = pool.map(processar_imagem, lista_de_imagens)
Async com FastAPI e banco de dados
from fastapi import FastAPI
import asyncpg # driver async para PostgreSQL
app = FastAPI()
pool = None
@app.on_event("startup")
async def startup():
global pool
pool = await asyncpg.create_pool("postgresql://user:senha@host/banco")
@app.get("/usuarios/{id}")
async def buscar_usuario(id: int):
async with pool.acquire() as conn:
usuario = await conn.fetchrow("SELECT * FROM usuarios WHERE id=$1", id)
return dict(usuario)
Quando usar e quando não usar
- Use async quando o gargalo é I/O: chamadas HTTP, banco, leitura de arquivo
- Não use async quando o gargalo é CPU: processamento numérico, machine learning, compressão
- Em Django: suporte a async existe desde o 3.1 mas o ORM ainda tem limitações — prefira usar em views específicas que fazem chamadas HTTP externas
Próximos passos
Com todos esses conceitos, o último passo é garantir que sua aplicação Django chegue ao ar corretamente — e é o que o checklist de deploy cobre.