Módulo 9 - Async e asyncio
Rodando várias tarefas com gather
10 min de leitura · por Cesar Gargiulo, revisado pela equipe ValorFinal e GuardiaSec · Atualizado em 01/07/2026
O que você vai aprender
- Disparar várias corrotinas juntas com asyncio.gather.
- Entender por que gather sobrepõe as esperas e reduz o tempo total.
- Coletar os resultados na ordem das corrotinas passadas.
- Conhecer async for e async with em linhas gerais.
Ouvir o resumo desta aula
Um recap de cerca de 2 minutos na voz do Valim, para ouvir no trânsito ou na academia.
Ler a transcrição do resumo
Resumo da aula: Rodando várias tarefas com gather.
Os objetivos desta aula. Disparar várias corrotinas juntas com asyncio.gather. Entender por que gather sobrepõe as esperas e reduz o tempo total. Coletar os resultados na ordem das corrotinas passadas. Conhecer async for e async with em linhas gerais.
Veja o essencial, parte por parte.
Disparar tudo ao mesmo tempo. asyncio.gather recebe várias corrotinas e as roda ao mesmo tempo, esperando todas.
O ganho e a ordem dos resultados. O ganho de tempo é o mesmo que você viu com threads, por um motivo idêntico: as esperas de I/O passam a acontecer em paralelo lógico.
Uma olhada em async for e async with. Para rodar várias corrotinas ao mesmo tempo, use asyncio.gather e deixe as esperas se sobreporem.
Esse foi o resumo do essencial. Para se aprofundar, leia a aula completa e responda os exercícios.
Disparar tudo ao mesmo tempo
Na aula anterior vimos que aguardar corrotinas em sequência não dá concorrência: cada await espera uma terminar antes da próxima começar. O asyncio.gather resolve isso. Você passa várias corrotinas a ele e o gather as entrega todas de uma vez ao event loop, que começa a tocar cada uma. Assim, enquanto a primeira dorme no seu asyncio.sleep, a segunda já está dormindo no dela, e a terceira também. Todas as esperas acontecem ao mesmo tempo, sobrepostas. O gather então aguarda até a última terminar e devolve os resultados de todas em uma lista, na mesma ordem em que você as passou, independentemente de qual terminou primeiro.
import asyncio
import time
async def buscar(nome, segundos):
await asyncio.sleep(segundos)
return f"{nome} pronto"
async def main():
inicio = time.perf_counter()
resultados = await asyncio.gather(
buscar("A", 1),
buscar("B", 1),
buscar("C", 1),
)
print(resultados)
print(f"tempo: {time.perf_counter() - inicio:.1f}s") # ~1s, nao 3
asyncio.run(main())gather roda as três juntas; as esperas se sobrepõem e o total fica ~1s, não 3s.
O ganho e a ordem dos resultados
O ganho de tempo é o mesmo que você viu com threads, por um motivo idêntico: as esperas de I/O passam a acontecer em paralelo lógico. Três chamadas de um segundo, feitas em sequência, somam três segundos; com gather, elas se sobrepõem e o total cai para pouco mais de um, o tempo da mais demorada. Quanto mais tarefas de espera você tem, maior o ganho. Um detalhe que costuma tranquilizar: mesmo que as tarefas terminem em ordem embaralhada, o gather devolve os resultados na ordem das corrotinas que você passou. A primeira posição da lista é sempre o resultado da primeira corrotina, e assim por diante.
Isso torna o gather muito prático para processar coleções. Se você tem uma lista de itens e uma corrotina que processa cada um, pode gerar as corrotinas com uma compreensão de lista e passar todas ao gather de uma vez, desempacotando com o operador de estrela. O resultado é uma lista alinhada com a de entrada, pronta para usar. Um cuidado importante: por padrão, se uma das corrotinas lançar uma exceção, o gather propaga esse erro. Existe a opção return_exceptions para, em vez de propagar, devolver a exceção como se fosse um resultado, útil quando você quer processar o que deu certo e tratar as falhas depois.
import asyncio
async def dobro(n):
await asyncio.sleep(0.2)
return n * 2
async def main():
numeros = [1, 2, 3, 4, 5]
corrotinas = [dobro(n) for n in numeros]
resultados = await asyncio.gather(*corrotinas)
print(resultados) # [2, 4, 6, 8, 10], na ordem de entrada
asyncio.run(main())Gerar uma corrotina por item e desempacotar com *: resultados alinhados com a entrada.
Uma olhada em async for e async with
Para fechar o panorama do async, vale conhecer duas construções que você vai encontrar em bibliotecas assíncronas, mesmo sem usá-las já. O async with é a versão assíncrona do gerenciador de contexto: ele abre e fecha um recurso com etapas que podem aguardar, como estabelecer e encerrar uma conexão de rede sem travar o loop. Você o usa igual ao with comum, só com a palavra async na frente. O async for percorre um iterável assíncrono, aquele que produz cada item com uma espera pelo meio, como uma resposta de rede que chega em pedaços. Em ambos, a ideia é a mesma do resto do módulo: onde houver espera, ela é sinalizada com await e o event loop aproveita para adiantar outras tarefas.
import asyncio
# um iteravel assincrono simples, so para ilustrar a sintaxe
async def contador(ate):
for i in range(ate):
await asyncio.sleep(0.1)
yield i
async def main():
async for numero in contador(3): # percorre com esperas pelo meio
print("recebi", numero)
asyncio.run(main())async for percorre um gerador assíncrono; cada item chega após um await.
Teste rápido
Por que asyncio.gather com três esperas de 1 segundo termina em cerca de 1 segundo, e não em 3?
Perguntas frequentes
- Qual a diferença entre await sequencial e asyncio.gather?
- Aguardar corrotinas uma após a outra com await as executa em sequência, somando os tempos. O asyncio.gather agenda todas ao mesmo tempo no event loop, sobrepondo as esperas, então o total cai para o da mais demorada. Para ganhar tempo com várias tarefas de I/O, use o gather.
- A ordem dos resultados do gather segue a ordem de término?
- Não. O gather devolve os resultados na ordem das corrotinas que você passou, não na ordem em que terminaram. A primeira posição da lista corresponde sempre à primeira corrotina. Isso torna o gather previsível para processar coleções alinhadas com a entrada.
- O que acontece se uma corrotina no gather lançar uma exceção?
- Por padrão, o gather propaga essa exceção, interrompendo a espera pelos resultados. Se você quiser que ele devolva a exceção como um resultado, em vez de propagar, passe return_exceptions igual a True. Aí você processa o que deu certo e trata as falhas depois, item a item.
- gather é o mesmo que criar tasks?
- São aparentados. O gather agenda as corrotinas como tasks internamente e espera todas terminarem, entregando os resultados juntos. Criar tasks manualmente dá mais controle, por exemplo para começar uma tarefa e continuar fazendo outras coisas antes de aguardá-la. Para o caso comum de disparar tudo e esperar, o gather é o mais direto.
- Preciso dominar async for e async with agora?
- Não. Nesta aula eles entram como panorama, para você reconhecê-los ao ler código de bibliotecas assíncronas. A ideia central já está firme: onde há espera, marca-se com await e o event loop aproveita. Quando usar um cliente assíncrono de rede ou banco, você verá essas construções e entenderá o porquê.
Fontes
Seu progresso fica salvo neste aparelho. Assinantes sincronizam entre os aparelhos.