Pular para o conteúdo principal

M17 — Async/await

Bloco 8 — Reuso e integração · Eixo B · Nível NÍVEL 1 + NÍVEL 2 · LAB

Abertura

Até agora, todo código do curso roda de forma sequencial: uma linha termina antes da próxima começar. Isso é simples, mas ineficiente quando o programa precisa esperar por algo externo — como uma resposta de rede. Este é o último módulo do Eixo B antes do Agent SDK (M18), e o conceito que ele introduz é essencial para entender como um agente pode fazer várias coisas "ao mesmo tempo" sem travar.

Conceito central

Código síncrono executa uma instrução de cada vez, esperando cada uma terminar. Quando uma operação envolve espera — uma requisição de rede, a leitura de um arquivo grande — o programa fica parado sem fazer nada útil enquanto espera.

Código assíncrono permite que o programa inicie uma operação de espera e, enquanto ela não termina, siga fazendo outras coisas — é uma operação não bloqueante. Em Python, isso é expresso com as palavras-chave async e await: uma função declarada com async def é uma corrotina; dentro dela, await marca o ponto onde o programa pode "ceder a vez" enquanto espera algo terminar.

É importante deixar claro: assíncrono não significa paralelo (rodando ao mesmo tempo em núcleos diferentes de processador) — significa não bloquear enquanto espera, o que é diferente e mais sutil. Conectando com o Eixo A: quando o Claude Code executa múltiplas ferramentas ou subagents em paralelo (M13), o mecanismo por trás é conceitualmente aparentado a isso — não ficar parado esperando uma coisa terminar para começar a próxima.

Na prática

O sandbox desta página roda um interpretador Python simplificado (Pyodide) dentro do navegador. Ele suporta async/await e asyncio.sleep/asyncio.gather, mas não suporta asyncio.run() do mesmo jeito que o Python instalado no seu computador — porque o próprio sandbox já roda dentro do equivalente a um loop assíncrono. Por isso, os exemplos abaixo usam await diretamente, sem asyncio.run(). No seu computador, ou no Claude Code, os dois estilos funcionam. Vale notar também que este sandbox roda numa única thread do navegador — então os exemplos abaixo mostram bem a diferença entre não bloquear (o que asyncio faz de verdade) e rodar em núcleos de processador diferentes ao mesmo tempo (o que asyncio não faz): aqui não existe a menor chance de duas tarefas rodarem literalmente ao mesmo tempo, só de alternar entre elas de forma eficiente.

Duas esperas rodando uma depois da outra — o tempo total é a soma das duas:

As mesmas duas esperas, agora rodando ao mesmo tempo com asyncio.gather() — o tempo total é o da mais demorada, não a soma:

Repare no resultado: a primeira versão leva cerca de 2 segundos (1 + 1, em sequência); a segunda leva cerca de 1 segundo (as duas esperas acontecem ao mesmo tempo). O código de cada consulta não mudou — o que mudou foi como elas foram organizadas para rodar.

O exemplo anterior mostra o ganho de tempo, mas não prova que as duas tarefas rodam "ao mesmo tempo" de verdade. Este aqui mostra a concorrência por dentro, imprimindo o início e o fim de cada tarefa:

Repare na ordem dos prints: as duas tarefas "começam" quase juntas, mas "terminam" na ordem do próprio tempo de espera de cada uma — Tarefa B (1s) antes de Tarefa A (2s). Isso só é possível porque existe uma única linha de execução alternando entre as duas nos pontos marcados por await: nunca duas rodando ao mesmo tempo em núcleos diferentes do processador (isso seria paralelismo, M13), e sim uma cedendo a vez para a outra enquanto espera — concorrência.

Diagrama

Para onde vai o tempo de espera, em cada versão:

Sequencial vs. concorrente: para onde vai o tempo de espera
Etapa 1 de 5

Lab opcional 🔵

Opcional — para assinantes

Escreva duas corrotinas que simulam chamadas demoradas (com asyncio.sleep) e execute-as concorrentemente com asyncio.gather(), comparando o tempo total com a versão sequencial.

Checkpoint