Python Programação Assíncrona (asyncio)
Muitos programas passam a maior parte do tempo esperando: a resposta de um site, um arquivo que vem pela rede, um banco de dados que demora a responder. Enquanto espera, um programa comum fica parado, sem fazer mais nada. A programação assíncrona ensina o programa a aproveitar essas esperas: começar outra tarefa enquanto a primeira não responde. Em Python, ela se faz com duas palavras novas, async e await, e com o módulo asyncio. É o assunto mais avançado deste nível; vá devagar, rodando cada exemplo.
- A ideia: uma pessoa só, alternando tarefas nas esperas.
- A escrever uma corrotina com
async defe a esperar comawait. - A rodar tudo com
asyncio.run(). - A fazer várias esperas ao mesmo tempo com
asyncio.gather(), e por que o tempo total cai. create_task,TaskGroupewait_for(tempo limite).- O que acontece quando uma das tarefas dá erro.
- Os erros e avisos reais:
coroutine was never awaited,'await' outside function,a coroutine or an awaitable is requirede otime.sleepque trava tudo.
A ideia: a cozinheira que não fica olhando o forno
A Ana prepara o café da manhã: o pão leva 2 minutos no forno, e o café leva 1 minuto para a água ferver. Há dois jeitos de fazer:
- Um de cada vez (síncrono): põe o pão no forno e fica parada olhando até ficar pronto (2 min). Só então põe a água do café e espera ferver (1 min). Total: 3 minutos.
- Aproveitando a espera (assíncrono): põe o pão no forno e, enquanto ele assa, põe a água do café. O café fica pronto no primeiro minuto, o pão no segundo. Total: 2 minutos.
Repare: continua sendo uma pessoa só. Ela não fica mais rápida nem ganha um par de braços; ela só não desperdiça a espera. É isso que o asyncio faz: um programa, uma linha de execução, alternando entre tarefas toda vez que uma delas diz "agora vou esperar".
| Na cozinha | No Python |
|---|---|
| Uma receita que tem esperas no meio | Uma corrotina: async def assar_pao(): |
| "Agora vou esperar; pode cuidar de outra coisa" | await (em inglês, "aguardar") |
| Esperar o forno | await asyncio.sleep(2) |
| A cozinheira, que decide o que fazer a cada momento | O laço de eventos (event loop) do asyncio |
| Abrir a cozinha e começar o dia | asyncio.run(main()) |
| "Faça o pão e o café juntos" | await asyncio.gather(assar_pao(), fazer_cafe()) |
1. O jeito de sempre: um de cada vez
Para comparar, o café da manhã com funções comuns. O time.sleep(segundos) faz o programa dormir (usamos segundos no lugar dos minutos, para não esperar tanto), e o time.perf_counter() mede o tempo, como no @cronometro da aula Decoradores:
import time
def fazer_cafe():
print("Café: pus a água para ferver")
time.sleep(1)
print("Café: pronto")
def assar_pao():
print("Pão: pus no forno")
time.sleep(2)
print("Pão: pronto")
inicio = time.perf_counter()
assar_pao()
fazer_cafe()
print(f"Café da manhã em {time.perf_counter() - inicio:.1f} s")
$ python3 sincrono.py Pão: pus no forno Pão: pronto Café: pus a água para ferver Café: pronto Café da manhã em 3.0 s
2 + 1 = 3 segundos. Durante os 2 segundos do forno, o programa não fez absolutamente nada.
2. A primeira corrotina: async def, await e asyncio.run
import asyncio
async def dizer_ola():
print("Olá...")
await asyncio.sleep(1)
print("...do mundo assíncrono!")
asyncio.run(dizer_ola())
$ python3 primeira.py Olá... ...do mundo assíncrono!
- import asyncio
- O módulo da programação assíncrona. Vem com o Python.
- async def dizer_ola():
asyncna frente dodefcria uma corrotina: uma função que pode pausar no meio para esperar, e deixar outras corrotinas trabalharem enquanto isso. (Lembra oyieldda aula Geradores, que também pausa; as corrotinas nasceram dos geradores.)- await asyncio.sleep(1)
- "Aguarde 1 segundo". É a versão assíncrona do
time.sleep: em vez de travar o programa, ela avisa o laço de eventos "vou esperar 1 segundo; se tiver outra tarefa, cuide dela". Oawaitsó pode aparecer dentro de umasync def. - asyncio.run(dizer_ola())
- Liga o laço de eventos, roda a corrotina até o fim e desliga. É a porta de entrada: normalmente aparece uma vez, na última linha do programa.
Chamar uma corrotina não a executa
Como nos geradores, chamar dizer_ola() não roda nenhuma linha: só cria um objeto corrotina, que precisa ser entregue ao asyncio:
import asyncio
async def dizer_ola():
print("Olá!")
resultado = dizer_ola()
print(resultado)
print(type(resultado))
asyncio.run(resultado)
$ python3 corrotina.py <coroutine object dizer_ola at 0x7f2eac4c1c00> « o endereço muda a cada execução <class 'coroutine'> Olá!
O Olá! só apareceu na última linha, quando o asyncio.run executou a corrotina. Esquecer o asyncio.run (ou o await) é o erro mais comum; ele está em Quando dá errado.
3. Só await não basta: ainda é um de cada vez
O café da manhã com corrotinas, cada uma com o seu await asyncio.sleep. A main ("principal") é a corrotina que organiza as outras, um costume do asyncio:
import asyncio
import time
async def fazer_cafe():
print("Café: pus a água para ferver")
await asyncio.sleep(1)
print("Café: pronto")
return "café"
async def assar_pao():
print("Pão: pus no forno")
await asyncio.sleep(2)
print("Pão: pronto")
return "pão"
async def main():
inicio = time.perf_counter()
await assar_pao()
await fazer_cafe()
print(f"Café da manhã em {time.perf_counter() - inicio:.1f} s")
asyncio.run(main())
$ python3 sequencial.py Pão: pus no forno Pão: pronto Café: pus a água para ferver Café: pronto Café da manhã em 3.0 s
Os mesmos 3 segundos! await assar_pao() quer dizer "espere o pão terminar, e só então siga para a próxima linha". A main esperou o pão inteiro antes de começar o café. Para fazer as duas ao mesmo tempo, é preciso dizer isso ao asyncio.
4. asyncio.gather: várias ao mesmo tempo
asyncio.gather(…) ("reunir") recebe várias corrotinas, começa todas e espera todas terminarem. Só a main muda:
import asyncio
import time
async def fazer_cafe():
print("Café: pus a água para ferver")
await asyncio.sleep(1)
print("Café: pronto")
return "café"
async def assar_pao():
print("Pão: pus no forno")
await asyncio.sleep(2)
print("Pão: pronto")
return "pão"
async def main():
inicio = time.perf_counter()
resultados = await asyncio.gather(assar_pao(), fazer_cafe())
print("Na mesa:", resultados)
print(f"Café da manhã em {time.perf_counter() - inicio:.1f} s")
asyncio.run(main())
$ python3 gather.py Pão: pus no forno Café: pus a água para ferver Café: pronto Pão: pronto Na mesa: ['pão', 'café'] Café da manhã em 2.0 s
- O
gathercomeça o pão:Pão: pus no forno. Noawait asyncio.sleep(2), o pão diz "vou esperar". - O laço de eventos aproveita e começa o café:
Café: pus a água para ferver. Noawait asyncio.sleep(1), o café também espera. - Com 1 segundo, a espera do café acaba primeiro:
Café: pronto. - Com 2 segundos, a do pão:
Pão: pronto. - O
gatherdevolve a lista com o que cada corrotina devolveu (oreturn), na ordem em que foram passadas (pão, café), e não na ordem em que terminaram.
Tempo total: 2 segundos, o tempo da espera mais longa, e não a soma. (Os números do tempo podem variar um pouco, 2.0 ou 2.1, conforme o computador.)
Missão: a main espera o pão terminar antes de começar o café, e por isso o café fica pronto depois do pão. Troque as duas linhas de await por uma só: await asyncio.gather(assar_pao(), fazer_cafe()). O Café: pronto tem de aparecer antes do Pão: pronto.
import asyncio
async def fazer_cafe():
print("Café: pus a água para ferver")
await asyncio.sleep(1)
print("Café: pronto")
async def assar_pao():
print("Pão: pus no forno")
await asyncio.sleep(2)
print("Pão: pronto")
async def main():
await assar_pao()
await fazer_cafe()
asyncio.run(main())
import asyncio
async def fazer_cafe():
print("Café: pus a água para ferver")
await asyncio.sleep(1)
print("Café: pronto")
async def assar_pao():
print("Pão: pus no forno")
await asyncio.sleep(2)
print("Pão: pronto")
async def main():
await asyncio.gather(assar_pao(), fazer_cafe())
asyncio.run(main())
Cem esperas, quase o tempo de uma
A vantagem cresce com o número de tarefas. Cem entregas, cada uma esperando 0,1, 0,2 ou 0,3 segundo (o *tarefas desempacota a lista em argumentos, como na aula *args e **kwargs):
import asyncio
import time
async def entregar(pedido, minutos):
await asyncio.sleep(minutos / 10)
return f"pedido {pedido} entregue"
async def main():
inicio = time.perf_counter()
tarefas = [entregar(n, n % 3 + 1) for n in range(1, 101)]
resultados = await asyncio.gather(*tarefas)
print(len(resultados), "entregas")
print(resultados[:3])
print(f"Tempo total: {time.perf_counter() - inicio:.1f} s")
asyncio.run(main())
$ python3 muitos.py 100 entregas ['pedido 1 entregue', 'pedido 2 entregue', 'pedido 3 entregue'] Tempo total: 0.3 s
Somadas, as esperas dariam uns 20 segundos. Juntas, levaram 0,3 s: o tempo da mais longa. É assim que um programa assíncrono consegue, por exemplo, baixar cem páginas da internet quase no tempo de uma: o tempo todo é espera pela rede.
5. Tarefas: começar agora, esperar depois
O gather começa e espera tudo de uma vez. Às vezes você quer começar algo e só esperar lá na frente, fazendo outras coisas no meio. Para isso existe asyncio.create_task(corrotina) ("criar tarefa"): ele agenda a corrotina e devolve uma tarefa, que você aguarda quando quiser.
import asyncio
async def lavar_roupa():
print("Máquina: começou")
await asyncio.sleep(2)
print("Máquina: terminou")
return "roupa limpa"
async def main():
maquina = asyncio.create_task(lavar_roupa())
print("Enquanto a máquina bate, a Ana arruma a cozinha.")
await asyncio.sleep(1)
print("Cozinha arrumada.")
roupa = await maquina
print("Resultado:", roupa)
asyncio.run(main())
$ python3 tarefas.py Enquanto a máquina bate, a Ana arruma a cozinha. Máquina: começou Cozinha arrumada. Máquina: terminou Resultado: roupa limpa
Repare na ordem: a frase da Ana veio antes de Máquina: começou. O create_task só agenda; a tarefa começa de verdade no primeiro await da main, quando a main para de trabalhar e deixa o laço de eventos cuidar das outras. Lá no fim, await maquina espera a máquina terminar e pega o return dela.
TaskGroup: um grupo de tarefas
Desde o Python 3.11, o jeito recomendado de começar várias tarefas e esperar todas é o asyncio.TaskGroup ("grupo de tarefas"), um gerenciador de contexto (aula Gerenciadores de Contexto) usado com async with:
import asyncio
async def preparar(item, segundos):
await asyncio.sleep(segundos)
print(f"{item} pronto")
return item
async def main():
async with asyncio.TaskGroup() as grupo:
t1 = grupo.create_task(preparar("bolo", 2))
t2 = grupo.create_task(preparar("suco", 1))
print("Tudo pronto:", t1.result(), t2.result())
asyncio.run(main())
$ python3 taskgroup.py suco pronto bolo pronto Tudo pronto: bolo suco
O bloco async with só termina quando todas as tarefas do grupo terminaram; depois dele, .result() dá o que cada uma devolveu. A diferença para o gather aparece quando algo dá errado: se uma tarefa do grupo falha, o TaskGroup cancela as outras, em vez de deixá-las rodando soltas.
wait_for: tempo limite
Esperar, sim; para sempre, não. asyncio.wait_for(corrotina, timeout=segundos) desiste depois do tempo dado e lança TimeoutError ("erro de tempo esgotado"), que você trata com try/except (aula try e except):
import asyncio
async def esperar_entregador():
await asyncio.sleep(5)
return "pizza"
async def main():
try:
pizza = await asyncio.wait_for(esperar_entregador(), timeout=1)
print("Chegou:", pizza)
except TimeoutError:
print("Demorou demais: vou fazer um sanduíche.")
asyncio.run(main())
$ python3 timeout.py Demorou demais: vou fazer um sanduíche.
O programa terminou em 1 segundo, e não em 5: o wait_for cancelou a espera do entregador.
6. Quando uma das tarefas dá erro
import asyncio
async def assar_pao():
await asyncio.sleep(1)
return "pão"
async def fazer_cafe():
await asyncio.sleep(0.5)
raise ValueError("acabou o pó de café")
async def main():
resultados = await asyncio.gather(assar_pao(), fazer_cafe(), return_exceptions=True)
print(resultados)
resultados = await asyncio.gather(assar_pao(), fazer_cafe())
print(resultados)
asyncio.run(main())
$ python3 erro_gather.py
['pão', ValueError('acabou o pó de café')]
Traceback (most recent call last):
File "/home/ana/erro_gather.py", line 21, in <module>
asyncio.run(main())
~~~~~~~~~~~^^^^^^^^
File "/usr/lib/python3.14/asyncio/runners.py", line 204, in run
return runner.run(main)
~~~~~~~~~~^^^^^^
«… mais 6 linhas de dentro do asyncio …»
File "/home/ana/erro_gather.py", line 17, in main
resultados = await asyncio.gather(assar_pao(), fazer_cafe())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/ana/erro_gather.py", line 11, in fazer_cafe
raise ValueError("acabou o pó de café")
ValueError: acabou o pó de café
- Com
return_exceptions=True("devolver as exceções"), o erro do café não para nada: ele vira um item da lista de resultados, no lugar do café. O pão chegou normalmente. Depois, você confere cada item. - Sem essa opção, o primeiro erro sobe pelo
awaitdogathere, como ninguém trata, encerra o programa. - O traceback passa por várias linhas de dentro do
asyncio(oasyncio.run, o laço de eventos). Para entender o erro, olhe as linhas que são do seu arquivo, de baixo para cima: a linha 11, nafazer_cafe, lançou o erro; a linha 17, namain, estava esperando nogather. No editor do guia, o traceback sai igual a este.
Formas diferentes de esperar várias coisas
| Forma | Quando usar |
|---|---|
await a() e depois await b() | Quando b precisa do resultado de a (não dá para começar o recheio antes de assar a massa) |
await asyncio.gather(a(), b()) | Esperas independentes, e você quer a lista dos resultados na ordem |
asyncio.create_task(a()) … await tarefa | Começar agora e aguardar lá na frente, fazendo outra coisa no meio |
async with asyncio.TaskGroup() | Várias tarefas, e, se uma falhar, as outras devem ser canceladas (3.11+) |
asyncio.wait_for(a(), timeout=n) | Desistir depois de n segundos |
asyncio ajuda (e quando não)
O asyncio acelera programas que passam o tempo esperando coisas de fora: rede, sites, bancos de dados, muitos arquivos. Ele não acelera contas pesadas (somar um bilhão de números, processar imagens): ali não há espera para aproveitar, a cozinheira está o tempo todo com as mãos ocupadas. E só funciona com funções que sabem "esperar sem travar": o asyncio.sleep sim, o time.sleep não (veja abaixo). Para a rede, existem bibliotecas assíncronas instaláveis com o pip; elas não funcionam no editor do guia, que não tem acesso à rede.
Quando dá errado
time.sleep dentro de uma corrotina: trava todo mundo
import asyncio
import time
async def fazer_cafe():
print("Café: pus a água para ferver")
time.sleep(1)
print("Café: pronto")
async def assar_pao():
print("Pão: pus no forno")
time.sleep(2)
print("Pão: pronto")
async def main():
inicio = time.perf_counter()
await asyncio.gather(assar_pao(), fazer_cafe())
print(f"Café da manhã em {time.perf_counter() - inicio:.1f} s")
asyncio.run(main())
$ python3 bloqueia.py Pão: pus no forno Pão: pronto Café: pus a água para ferver Café: pronto Café da manhã em 3.0 s
Tem async, tem gather, e mesmo assim levou 3 segundos, um de cada vez. Nenhum erro, nenhum aviso. O time.sleep não sabe nada de asyncio: ele trava o programa inteiro, e o laço de eventos não tem chance de começar o café. Só o await devolve a vez. Como consertar: dentro de async def, use await asyncio.sleep(…). A regra vale para tudo que demora: precisa ser uma versão assíncrona, chamada com await.
await
import asyncio
import time
async def main():
inicio = time.perf_counter()
print("Esperando 1 segundo...")
asyncio.sleep(1)
print(f"Esperei {time.perf_counter() - inicio:.1f} s")
asyncio.run(main())
$ python3 sem_await.py Esperando 1 segundo... /home/ana/sem_await.py:8: RuntimeWarning: coroutine 'sleep' was never awaited asyncio.sleep(1) RuntimeWarning: Enable tracemalloc to get the object allocation traceback Esperei 0.0 s
"Aviso de execução: a corrotina sleep nunca foi aguardada". Sem await, asyncio.sleep(1) só criou uma corrotina (como na seção 2) e a jogou fora: não esperou nada, como mostra o 0.0 s. O aviso diz o arquivo e a linha (8). A segunda linha do aviso, sobre o tracemalloc, é uma ferramenta de investigação de memória e pode ser ignorada. Como consertar: await asyncio.sleep(1).
main() sem o asyncio.run
import asyncio
async def main():
print("O café da manhã começou.")
await asyncio.sleep(1)
main()
print("Fim do programa.")
$ python3 sem_run.py /home/ana/sem_run.py:9: RuntimeWarning: coroutine 'main' was never awaited main() RuntimeWarning: Enable tracemalloc to get the object allocation traceback Fim do programa.
O mesmo aviso, agora para a main. O O café da manhã começou. nunca apareceu: a corrotina foi criada na linha 9 e nunca executada. Como consertar: asyncio.run(main()).
await fora de uma corrotina
import asyncio
print("Esperando...")
await asyncio.sleep(1)
$ python3 await_fora.py
File "/home/ana/await_fora.py", line 4
await asyncio.sleep(1)
^^^^^^^^^^^^^^^^^^^^^^
SyntaxError: 'await' outside function
"await fora de função". É um erro de sintaxe: o Python recusa o arquivo antes de rodar qualquer linha (repare que nem o Esperando... apareceu, e não há a linha Traceback). O await só existe dentro de async def. Como consertar: ponha o código numa async def main(): e rode com asyncio.run(main()).
asyncio.run(main), sem os parênteses
$ python3 run_sem_parenteses.py
Traceback (most recent call last):
File "/home/ana/run_sem_parenteses.py", line 8, in <module>
asyncio.run(main)
~~~~~~~~~~~^^^^^^
File "/usr/lib/python3.14/asyncio/runners.py", line 204, in run
return runner.run(main)
~~~~~~~~~~^^^^^^
File "/usr/lib/python3.14/asyncio/runners.py", line 103, in run
raise TypeError('An asyncio.Future, a coroutine or an '
'awaitable is required')
TypeError: An asyncio.Future, a coroutine or an awaitable is required
"É preciso um Future do asyncio, uma corrotina ou algo aguardável". Sem parênteses, foi a função main, e não a corrotina que ela cria (a mesma diferença entre f e f() da aula Decoradores). Como consertar: asyncio.run(main()).
Referência rápida
| Escrita | O que faz |
|---|---|
async def nome(): | Define uma corrotina; chamá-la cria um objeto coroutine, sem rodar |
await algo | Espera algo terminar, deixando outras tarefas rodarem; só dentro de async def |
asyncio.run(main()) | Liga o laço de eventos e roda a corrotina principal até o fim |
await asyncio.sleep(s) | Espera s segundos sem travar as outras |
await asyncio.gather(a(), b()) | Roda juntas; devolve a lista dos resultados na ordem dos argumentos |
gather(…, return_exceptions=True) | Os erros viram itens da lista, em vez de subir |
t = asyncio.create_task(a()) / await t | Agenda agora / espera e pega o resultado depois |
async with asyncio.TaskGroup() as g: | Grupo de tarefas (g.create_task); espera todas no fim do bloco (3.11+) |
await asyncio.wait_for(a(), timeout=s) | Desiste depois de s segundos com TimeoutError |
| Sintoma | Causa provável |
|---|---|
RuntimeWarning: coroutine '…' was never awaited | Faltou o await, ou o asyncio.run |
<coroutine object … at 0x…> no print | Mostrou a corrotina sem executá-la |
SyntaxError: 'await' outside function | await fora de um async def |
TypeError: An asyncio.Future, a coroutine or an awaitable is required | asyncio.run(main) sem os parênteses |
O gather não ficou mais rápido | time.sleep (ou outra função que trava) dentro da corrotina |
| Ainda um de cada vez | await a() seguido de await b(): use gather ou tarefas |
TimeoutError | O wait_for esgotou o tempo: trate com try/except |
Exercícios
1. A palavra da corrotina
Complete para criar uma corrotina:
2. Esperar sem travar
Complete a linha que espera 2 segundos dentro de uma corrotina, deixando as outras tarefas rodarem:
3. A porta de entrada
Complete a última linha do programa, que roda a corrotina main:
4. Três corrotinas esperam 1, 2 e 3 segundos com asyncio.sleep e rodam com gather. Quanto tempo leva, mais ou menos?
5. await asyncio.gather(pao(), cafe()), em que o café termina primeiro. Em que ordem vem a lista de resultados?
6. Em qual destes programas o asyncio ajuda mais?
Missão final: os pedidos da lanchonete
Missão: a lanchonete recebeu três pedidos: pizza (3 s), pastel (2 s) e suco (1 s). Rode e leia o aviso: o programa nem começou. Conserte duas coisas: (1) a última linha deve ser asyncio.run(main()); (2) na main, prepare os três ao mesmo tempo com pedidos = await asyncio.gather(…), na ordem pizza, pastel, suco, e mostre Pedidos prontos: com a lista. O suco deve ficar pronto primeiro.
import asyncio
async def preparar(item, segundos):
print(f"Começando: {item}")
await asyncio.sleep(segundos)
print(f"Pronto: {item}")
return item
async def main():
pizza = await preparar("pizza", 3)
pastel = await preparar("pastel", 2)
suco = await preparar("suco", 1)
print("Pedidos prontos:", [pizza, pastel, suco])
main()
import asyncio
async def preparar(item, segundos):
print(f"Começando: {item}")
await asyncio.sleep(segundos)
print(f"Pronto: {item}")
return item
async def main():
pedidos = await asyncio.gather(
preparar("pizza", 3),
preparar("pastel", 2),
preparar("suco", 1),
)
print("Pedidos prontos:", pedidos)
asyncio.run(main())
A solução mostra os três Começando: … de uma vez, depois Pronto: suco, Pronto: pastel e Pronto: pizza, nessa ordem, e por fim Pedidos prontos: ['pizza', 'pastel', 'suco']: a lista na ordem do gather, não na ordem em que ficaram prontos. Tudo em uns 3 segundos, em vez de 6. No terminal da Ana:
$ python3 lanche_sol.py Começando: pizza Começando: pastel Começando: suco Pronto: suco Pronto: pastel Pronto: pizza Pedidos prontos: ['pizza', 'pastel', 'suco']
Resumo
- Programação assíncrona é aproveitar as esperas: um programa só, alternando entre tarefas sempre que uma delas espera.
async defcria uma corrotina; chamá-la só cria o objeto. Ela roda comawait(dentro de outra corrotina) ou comasyncio.run(main())(na última linha do programa).await a()seguido deawait b()ainda é um de cada vez. Para juntos:asyncio.gather(a(), b()), que devolve os resultados na ordem dos argumentos; oucreate_taskeTaskGroup.wait_forpõe tempo limite (TimeoutError);return_exceptions=Truetransforma os erros dogatherem itens da lista.- Dentro de corrotina, nada de
time.sleep: ele trava tudo. Useawait asyncio.sleep. coroutine '…' was never awaitedquer dizer: faltou umawaitou oasyncio.run.