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.

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.