Python

Dominando async e await em Python: Padrões e Armadilhas Comuns em Projetos Reais

13 min de leitura

Dominando async e await em Python: Padrões e Armadilhas Comuns em Projetos Reais

Entendendo Async/Await: Fundamentos e Diferença do Síncrono Quando começamos a programar, aprendemos a escrever código sequencial: uma linha executa, depois a próxima, e assim por diante. Isso funciona bem para a maioria dos casos, mas há situações onde essa abordagem cria gargalos significativos. Imagine fazer múltiplas requisições HTTP ou operações de leitura de arquivo — seu programa ficaria parado esperando cada uma terminar antes de começar a próxima. A programação assíncrona resolve esse problema permitindo que seu código comece uma operação, a deixe "em progresso" e continue executando outras tarefas. Quando a operação termina, você retoma o trabalho. Em Python, e são as palavras-chave que tornam isso possível. define uma função assíncrona, enquanto pausa a execução até que um resultado chegue, sem bloquear o programa inteiro. Agora, veja como async/await muda isso: A diferença é clara: no primeiro caso, levamos 6 segundos porque cada operação espera a anterior terminar. No segundo, levamos apenas 2 segundos porque as três operações rodam

<h2>Entendendo Async/Await: Fundamentos e Diferença do Síncrono</h2>

<p>Quando começamos a programar, aprendemos a escrever código sequencial: uma linha executa, depois a próxima, e assim por diante. Isso funciona bem para a maioria dos casos, mas há situações onde essa abordagem cria gargalos significativos. Imagine fazer múltiplas requisições HTTP ou operações de leitura de arquivo — seu programa ficaria parado esperando cada uma terminar antes de começar a próxima.</p>

<p>A programação assíncrona resolve esse problema permitindo que seu código comece uma operação, a deixe &quot;em progresso&quot; e continue executando outras tarefas. Quando a operação termina, você retoma o trabalho. Em Python, <code>async</code> e <code>await</code> são as palavras-chave que tornam isso possível. <code>async</code> define uma função assíncrona, enquanto <code>await</code> pausa a execução até que um resultado chegue, sem bloquear o programa inteiro.</p>

<pre><code class="language-python"># Código síncrono (bloqueante)

import time

def buscar_dados(id):

time.sleep(2) # Simula I/O

return f&quot;Dados do usuário {id}&quot;

def processar_usuarios():

for i in range(3):

resultado = buscar_dados(i)

print(resultado)

inicio = time.time()

processar_usuarios()

print(f&quot;Tempo total: {time.time() - inicio:.2f}s&quot;) # ~6 segundos</code></pre>

<p>Agora, veja como async/await muda isso:</p>

<pre><code class="language-python"># Código assíncrono (não-bloqueante)

import asyncio

import time

async def buscar_dados(id):

await asyncio.sleep(2) # Simula I/O sem bloquear

return f&quot;Dados do usuário {id}&quot;

async def processar_usuarios():

tarefas = [buscar_dados(i) for i in range(3)]

resultados = await asyncio.gather(*tarefas)

for resultado in resultados:

print(resultado)

inicio = time.time()

asyncio.run(processar_usuarios())

print(f&quot;Tempo total: {time.time() - inicio:.2f}s&quot;) # ~2 segundos</code></pre>

<p>A diferença é clara: no primeiro caso, levamos 6 segundos porque cada operação espera a anterior terminar. No segundo, levamos apenas 2 segundos porque as três operações rodam <strong>concorrentemente</strong>. Isso não é paralelismo real (threads ou processos), é o gerenciador de eventos (event loop) do Python alternando entre tarefas de forma eficiente.</p>

<h2>Padrões Essenciais: Como Estruturar Código Assíncrono</h2>

<h3>O Event Loop: O Coração da Execução Assíncrona</h3>

<p>O event loop é um mecanismo que controla a execução de corrotinas no Python. Ele mantém uma fila de tarefas prontas para executar, e quando uma pausa (com <code>await</code>), o loop executa outra. Você raramente cria um event loop manualmente; <code>asyncio.run()</code> faz isso para você em scripts simples, mas é importante entender seu papel.</p>

<pre><code class="language-python">import asyncio

async def tarefa_a():

print(&quot;Tarefa A iniciada&quot;)

await asyncio.sleep(1)

print(&quot;Tarefa A concluída&quot;)

async def tarefa_b():

print(&quot;Tarefa B iniciada&quot;)

await asyncio.sleep(1)

print(&quot;Tarefa B concluída&quot;)

async def main():

Executa A e B concorrentemente

await asyncio.gather(tarefa_a(), tarefa_b())

asyncio.run(main())</code></pre>

<h3>Gather: Executando Múltiplas Corrotinas</h3>

<p><code>asyncio.gather()</code> é o seu aliado para executar várias corrotinas simultaneamente. Ele retorna uma lista com todos os resultados na mesma ordem das corrotinas passadas. Se uma corrotina lançar exceção, por padrão toda a operação falha — mas você pode controlar isso com <code>return_exceptions=True</code>.</p>

<pre><code class="language-python">async def buscar_usuario(id):

await asyncio.sleep(1)

if id == 2:

raise ValueError(f&quot;Usuário {id} não encontrado&quot;)

return f&quot;Usuário {id}&quot;

async def main():

Sem tratamento de erro

try:

resultados = await asyncio.gather(

buscar_usuario(1),

buscar_usuario(2),

buscar_usuario(3)

)

except ValueError as e:

print(f&quot;Erro capturado: {e}&quot;)

Com tratamento seguro

resultados = await asyncio.gather(

buscar_usuario(1),

buscar_usuario(2),

buscar_usuario(3),

return_exceptions=True

)

for i, resultado in enumerate(resultados):

if isinstance(resultado, Exception):

print(f&quot;Erro no índice {i}: {resultado}&quot;)

else:

print(f&quot;Sucesso: {resultado}&quot;)

asyncio.run(main())</code></pre>

<h3>Create Task: Agendando Tarefas com Mais Controle</h3>

<p>Enquanto <code>gather()</code> é conveniente, <code>asyncio.create_task()</code> oferece mais controle. Ele cria uma tarefa e a agenda para execução, retornando imediatamente um objeto <code>Task</code> que você pode aguardar depois. Isso é útil quando você precisa iniciar múltiplas operações sem esperar que uma termine antes de iniciar a próxima.</p>

<pre><code class="language-python">import asyncio

async def processar_pedido(id_pedido):

print(f&quot;Processando pedido {id_pedido}&quot;)

await asyncio.sleep(2)

print(f&quot;Pedido {id_pedido} concluído&quot;)

return f&quot;Resultado do pedido {id_pedido}&quot;

async def main():

Cria tarefas sem aguardar

tarefa1 = asyncio.create_task(processar_pedido(1))

tarefa2 = asyncio.create_task(processar_pedido(2))

Faz algo enquanto elas rodam

print(&quot;Tarefas iniciadas, fazendo outra coisa...&quot;)

await asyncio.sleep(0.5)

Aguarda ambas

resultado1 = await tarefa1

resultado2 = await tarefa2

print(resultado1, resultado2)

asyncio.run(main())</code></pre>

<h2>Armadilhas Comuns e Como Evitá-las</h2>

<h3>Armadilha 1: Esquecer de <code>await</code></h3>

<p>A armadilha mais clássica é chamar uma função assíncrona sem <code>await</code>. Você não receberá um erro imediato — apenas obterá um objeto corrotina não iniciado. Seu código continuará rodando, mas a operação nunca será executada. Isso deixa muitos iniciantes confusos.</p>

<pre><code class="language-python"># ❌ ERRADO

async def buscar_dados():

await asyncio.sleep(1)

return &quot;dados&quot;

async def main():

resultado = buscar_dados() # Sem await!

print(resultado) # &lt;coroutine object buscar_dados at 0x...&gt;

Aviso não suprimido!

asyncio.run(main())</code></pre>

<pre><code class="language-python"></code></pre>

<h3>Armadilha 2: Misturar Código Síncrono e Assíncrono</h3>

<p>Você não pode chamar <code>await</code> dentro de uma função normal (não assíncrona). Da mesma forma, operações bloqueantes (como <code>time.sleep()</code> ou requisições síncronas) travamtodo o event loop se executadas diretamente. Use bibliotecas assíncronas específicas.</p>

<pre><code class="language-python"># ❌ ERRADO: operação bloqueante em código assíncrono

import time

async def main():

time.sleep(2) # Bloqueia todo o event loop!

print(&quot;Finalmente&quot;)

asyncio.run(main())</code></pre>

<pre><code class="language-python"></code></pre>

<p>Se você realmente precisa executar código bloqueante, use <code>asyncio.to_thread()</code> (Python 3.9+) ou <code>loop.run_in_executor()</code>:</p>

<pre><code class="language-python">import asyncio

import time

def tarefa_bloqueante():

time.sleep(2)

return &quot;Pronto&quot;

async def main():

Executa em uma thread separada, sem bloquear o event loop

resultado = await asyncio.to_thread(tarefa_bloqueante)

print(resultado)

asyncio.run(main())</code></pre>

<h3>Armadilha 3: Exceções Silenciosas em Tarefas</h3>

<p>Quando você cria uma tarefa com <code>create_task()</code>, as exceções não propagam automaticamente. Se você nunca aguardar a tarefa, o erro passará despercebido até o garbage collector limpar a tarefa, momento em que Python imprimirá um aviso feio. Sempre aguarde suas tarefas ou use callbacks.</p>

<pre><code class="language-python"># ❌ ERRADO: exceção silenciosa

async def operacao_com_erro():

await asyncio.sleep(1)

raise ValueError(&quot;Algo deu errado&quot;)

async def main():

A tarefa é criada mas o erro nunca é tratado

tarefa = asyncio.create_task(operacao_com_erro())

print(&quot;Continuando...&quot;)

Se main() terminar sem aguardar tarefa, veremos um aviso feio

asyncio.run(main())</code></pre>

<pre><code class="language-python"></code></pre>

<h3>Armadilha 4: Deadlocks com Locks</h3>

<p>Usar <code>asyncio.Lock()</code> incorretamente pode criar deadlocks. A armadilha comum é esquecer de usar <code>async with</code> ou aguardar a aquisição do lock.</p>

<pre><code class="language-python"># ❌ ERRADO: pode causar deadlock

import asyncio

lock = asyncio.Lock()

async def tarefa():

await lock.acquire()

Se algum erro ocorrer aqui, lock nunca é liberado

await asyncio.sleep(1)

lock.release()

async def main():

await asyncio.gather(tarefa(), tarefa()) # Deadlock!

asyncio.run(main())</code></pre>

<pre><code class="language-python"></code></pre>

<h3>Armadilha 5: Assumir que Async é Mais Rápido Sempre</h3>

<p>Async brilha em operações I/O (rede, arquivos). Para computação CPU-intensiva, não ajuda — de fato, pode ser mais lento devido ao overhead. Use <code>asyncio.to_thread()</code> ou <code>multiprocessing</code> para CPU.</p>

<pre><code class="language-python">import asyncio

import time

CPU-intensiva

def fibonacci(n):

if n &lt; 2:

return n

return fibonacci(n-1) + fibonacci(n-2)

async def calcular():

Isso é ineficiente para CPU-intensiva

resultado = fibonacci(35)

return resultado

inicio = time.time()

asyncio.run(calcular())

print(f&quot;Tempo: {time.time() - inicio:.2f}s&quot;)

Melhor: use threads para isso

async def calcular_com_thread():

resultado = await asyncio.to_thread(fibonacci, 35)

return resultado

inicio = time.time()

asyncio.run(calcular_com_thread())

print(f&quot;Tempo: {time.time() - inicio:.2f}s&quot;)</code></pre>

<h2>Padrões Avançados: Timeout, Streaming e Contexto</h2>

<h3>Timeout com asyncio.wait_for()</h3>

<p>Operações assíncronas precisam de proteção contra travamentos infinitos. <code>asyncio.wait_for()</code> define um prazo máximo de espera.</p>

<pre><code class="language-python">import asyncio

async def operacao_lenta():

await asyncio.sleep(5)

return &quot;Concluído&quot;

async def main():

try:

resultado = await asyncio.wait_for(operacao_lenta(), timeout=2)

print(resultado)

except asyncio.TimeoutError:

print(&quot;Operação excedeu o tempo limite&quot;)

asyncio.run(main())</code></pre>

<h3>Streaming de Dados com Filas</h3>

<p>Para processar grandes volumes de dados, <code>asyncio.Queue</code> oferece um padrão produtor-consumidor eficiente.</p>

<pre><code class="language-python">import asyncio

async def produtor(fila):

for i in range(5):

await asyncio.sleep(1)

await fila.put(f&quot;Item {i}&quot;)

print(f&quot;Produzido: Item {i}&quot;)

async def consumidor(fila):

while True:

item = await fila.get()

if item is None:

break

print(f&quot;Consumido: {item}&quot;)

await asyncio.sleep(0.5)

fila.task_done()

async def main():

fila = asyncio.Queue()

Executa produtor e consumidor

await asyncio.gather(

produtor(fila),

consumidor(fila)

)

Sinaliza fim

await fila.put(None)

asyncio.run(main())</code></pre>

<h3>Context Managers Assíncrono</h3>

<p>Para recursos que precisam de setup e cleanup (conexões, transações), use <code>async with</code>.</p>

<pre><code class="language-python">import asyncio

class ConexaoDB:

async def __aenter__(self):

print(&quot;Conectando ao banco...&quot;)

await asyncio.sleep(1)

return self

async def __aexit__(self, exc_type, exc_val, exc_tb):

print(&quot;Desconectando do banco...&quot;)

await asyncio.sleep(1)

async def consulta(conexao):

print(&quot;Executando consulta...&quot;)

await asyncio.sleep(1)

return &quot;Resultado&quot;

async def main():

async with ConexaoDB() as db:

resultado = await consulta(db)

print(resultado)

asyncio.run(main())</code></pre>

<h2>Conclusão</h2>

<p>Nesta jornada, você aprendeu que <strong>async/await não é paralelismo, mas concorrência eficiente</strong> para operações I/O. O event loop é o maestro que alterna entre tarefas quando uma pausa com <code>await</code>, permitindo que múltiplas operações rodem sem bloquear a execução. Em segundo lugar, você identificou as <strong>armadilhas mais perigosas</strong>: esquecer de <code>await</code>, misturar código bloqueante com assíncrono, deixar tarefas com exceções silenciosas, criar deadlocks com locks e aplicar async onde não é apropriado. Por fim, entendeu que o real poder vem de <strong>padrões bem estruturados</strong> como <code>gather()</code>, <code>create_task()</code>, timeouts, filas e context managers, que transformam código assíncrono complexo em soluções elegantes e eficientes.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://docs.python.org/3/library/asyncio.html" target="_blank" rel="noopener noreferrer">Python asyncio documentation</a></li>

<li><a href="https://realpython.com/async-io-python/" target="_blank" rel="noopener noreferrer">Real Python: Async IO in Python: A Complete Walkthrough</a></li>

<li><a href="https://www.youtube.com/watch?v=iG6fr81xHKA" target="_blank" rel="noopener noreferrer">Miguel Grinberg: Asynchronous Python for the Complete Beginner</a></li>

<li><a href="https://www.python.org/dev/peps/pep-0492/" target="_blank" rel="noopener noreferrer">PEP 492 – Coroutines with async and await syntax</a></li>

<li><a href="https://www.youtube.com/watch?v=MCs5OvgHgAU" target="_blank" rel="noopener noreferrer">David Beazley: Python Concurrency From the Ground Up</a></li>

</ul>

<p>&lt;!-- FIM --&gt;</p>

Comentários

Mais em Python

Dominando asyncio Avançado em Python: Semáforos, Locks e Padrões de Concorrência em Projetos Reais
Dominando asyncio Avançado em Python: Semáforos, Locks e Padrões de Concorrência em Projetos Reais

Introdução: O Problema da Concorrência Controlada Quando trabalhamos com em P...

Boas Práticas de Estruturas de Controle em Python: if, match-case e Expressões para Times Ágeis
Boas Práticas de Estruturas de Controle em Python: if, match-case e Expressões para Times Ágeis

Introdução: O Controle de Fluxo como Base da Programação As estruturas de con...

Como Usar Redis com Python: Cache, Pub/Sub e Filas com redis-py em Produção
Como Usar Redis com Python: Cache, Pub/Sub e Filas com redis-py em Produção

Redis: O Fundamento Redis é um armazenamento de dados em memória (in-memory d...