Garanta sua assinatura com até 35%OFF antes do reajuste. Quero aproveitar
Asyncio no Python: eficiência no seu código

Rocketseat

Você já enfrentou momentos em que seu programa parece estar "travado", aguardando a resposta de uma requisição ou o término de uma tarefa? Esse é um desafio comum na programação síncrona, especialmente em aplicações que lidam com muitas operações de entrada/saída (I/O). É aqui que o asyncio no Python entra como uma solução poderosa para tornar seus programas mais eficientes e responsivos.
Neste artigo, vamos desmistificar o conceito de programação assíncrona e explorar como o asyncio pode melhorar o desempenho de suas aplicações. Prepare-se para mergulhar em exemplos práticos e entender como aplicar essa ferramenta essencial na sua jornada como dev!
O problema da programação síncrona
Imagine que você está em um restaurante movimentado. Você faz o pedido (uma requisição) e precisa esperar que a comida fique pronta antes de fazer qualquer outra coisa. Esse é o modelo da programação síncrona: as tarefas são executadas uma de cada vez, bloqueando o progresso enquanto uma operação não termina.
Agora, aplique isso a um programa que realiza múltiplas requisições a servidores web. Se cada requisição demora 2 segundos e você tem 5 URLs para processar, o tempo total será de 10 segundos. Durante esse tempo, o programa não consegue executar nenhuma outra tarefa, resultando em baixa eficiência. Nos dias de hoje, esperar 10 segundos por qualquer programa é uma eternidade e gera muita frustração. Ninguém tem paciência para isso.
Introdução à programação assíncrona
A programação assíncrona resolve esse problema ao permitir que o programa "siga em frente" enquanto aguarda a conclusão de uma tarefa de I/O. Voltando ao exemplo do restaurante, seria como se você pudesse ler um livro ou conversar com amigos enquanto espera sua comida chegar.
No Python, o asyncio é a ferramenta que facilita esse modelo. Ele permite criar tarefas que podem "pausar" sua execução, liberar o programa para outras operações e retomar o trabalho quando prontas. O resultado? Maior eficiência e responsividade, especialmente em aplicações que processam múltiplas requisições ou manipulações de dados simultaneamente.
Conceitos-chave do asyncio
Antes de colocar a mão na massa, é importante entender os pilares do asyncio:
1. async e await
Essas palavras-chave são o coração do asyncio.
- async: define uma função como corrotina, permitindo que ela pause sua execução.
- await: suspende a execução da corrotina atual até que outra tarefa seja concluída.
Exemplo básico:
import asyncio async def main(): print("Início") await asyncio.sleep(2) print("Fim") asyncio.run(main())
Saída:
Início (Faz uma pausa de 2 segundos) Fim
2. Event Loop (loop de eventos)
O event loop é como um maestro que gerencia a execução das corrotinas. Ele alterna entre as tarefas disponíveis, garantindo que o programa continue funcionando enquanto as operações de I/O estão em andamento.
Corrotina?
Uma corrotina é um tipo especial de função que pode ter sua execução suspensa e retomada posteriormente. Diferente de uma sub-rotina (função comum), que executa do começo ao fim sem interrupções, uma corrotina permite que o controle seja transferido para outras partes do programa durante sua execução, e depois retorne para continuar de onde parou.
3. asyncio.run()
Essa função inicia o loop de eventos e executa a corrotina principal. É a maneira recomendada de começar qualquer programa assíncrono.
Exemplos práticos com asyncio
Vamos transformar teoria em prática com dois exemplos que mostram o poder do asyncio.
Simulação de múltiplas requisições web
Aqui está como você pode processar várias requisições simultaneamente usando o asyncio:
import asyncio import time async def fetch_data(url): print(f"Fetching {url}") await asyncio.sleep(2) # Simula uma requisição print(f"Finished {url}") async def main(): urls = ["http://example.com/1", "http://example.com/2", "http://example.com/3"] tasks = [fetch_data(url) for url in urls] await asyncio.gather(*tasks) start_time = time.time() asyncio.run(main()) print(f"Tempo total: {time.time() - start_time:.2f} segundos")
Saída:
Fetching http://example.com/1 Fetching http://example.com/2 Fetching http://example.com/3 Finished http://example.com/1 Finished http://example.com/2 Finished http://example.com/3 Tempo total: 2.00 segundos
Sem asyncio, esse processo levaria 6 segundos. Com asyncio, todas as tarefas são executadas em paralelo, reduzindo o tempo total para apenas 2 segundos!
Uso com bibliotecas de terceiros
O asyncio brilha ainda mais quando combinado com bibliotecas como aiohttp, usada para requisições HTTP assíncronas:
import aiohttp import asyncio async def fetch(url): async with aiohttp.ClientSession() as session: async with session.get(url) as response: print(f"Status: {response.status}") return await response.text() async def main(): urls = ["https://jsonplaceholder.typicode.com/posts/1", "https://jsonplaceholder.typicode.com/posts/2"] tasks = [fetch(url) for url in urls] await asyncio.gather(*tasks) asyncio.run(main())
Esse trecho de código demonstra como utilizar o aiohttp, uma biblioteca assíncrona, para realizar requisições HTTP de forma eficiente. A função
fetch
cria uma sessão HTTP e faz uma requisição GET para uma URL, retornando o conteúdo da resposta. Já a função main
define uma lista de URLs e utiliza asyncio.gather
para executar várias requisições simultaneamente.Esse exemplo destaca o principal benefício da programação assíncrona: a habilidade de gerenciar múltiplas tarefas de I/O ao mesmo tempo, como requisições HTTP, sem bloquear a execução do programa. Assim, você obtém resultados mais rápidos e aproveita melhor os recursos do sistema.
Tratamento de exceções em código assíncrono
Erros podem acontecer, e o asyncio oferece formas eficientes de lidar com eles:
async def fetch_data(): try: await asyncio.sleep(1) raise ValueError("Erro simulado") except ValueError as e: print(f"Ocorreu um erro: {e}") asyncio.run(fetch_data())
Nesse exemplo, mostramos como lidar com exceções em um contexto assíncrono. A função
fetch_data
utiliza um bloco try...except
para capturar e tratar um erro intencionalmente levantado (ValueError
). Quando o erro ocorre, ele é capturado pelo bloco except
, permitindo que o programa continue sua execução de maneira controlada.Essa abordagem é essencial em programação assíncrona, pois garante que erros em uma corrotina sejam gerenciados corretamente, evitando que eles interrompam todo o fluxo do programa. Assim, você mantém a estabilidade e previsibilidade da aplicação, mesmo diante de falhas inesperadas.
Dicas e boas práticas para asyncio no Python
Trabalhar com programação assíncrona usando asyncio pode trazer ganhos significativos de desempenho e responsividade, mas é preciso cuidado para evitar armadilhas comuns. Confira estas dicas e boas práticas que vão ajudá-lo a tirar o máximo proveito dessa poderosa ferramenta:
1. Consistência assíncrona
Misturar código síncrono e assíncrono pode comprometer o desempenho do seu programa. Ao implementar asyncio, prefira bibliotecas e funções totalmente assíncronas para manter a fluidez no loop de eventos.
Exemplo Ruim:
import asyncio import requests # Biblioteca síncrona async def buscar_dados(url): resposta = requests.get(url) # Operação síncrona bloqueante return resposta.text async def main(): dados = await buscar_dados("https://www.exemplo.com") print(dados[:100]) asyncio.run(main())
Esse exemplo utiliza uma biblioteca síncrona (
requests
), que bloqueia o loop de eventos, anulando os benefícios da programação assíncrona.Exemplo Bom:
import asyncio import aiohttp # Biblioteca assíncrona async def buscar_dados(session, url): async with session.get(url) as resposta: return await resposta.text() async def main(): async with aiohttp.ClientSession() as session: dados = await buscar_dados(session, "https://www.exemplo.com") print(dados[:100]) asyncio.run(main())
Nesse caso, usamos aiohttp, uma biblioteca assíncrona que mantém o loop de eventos funcionando eficientemente.
2. Utilize bibliotecas assíncronas
Para maximizar os benefícios do asyncio, use bibliotecas que suportem nativamente operações assíncronas. Algumas opções incluem:
- aiohttp: para requisições HTTP.
- asyncpg ou aiomysql: para interações com bancos de dados PostgreSQL ou MySQL.
- asyncios.open_connection(): para trabalhar com sockets de forma assíncrona.
Por quê?
Bibliotecas síncronas bloqueiam o loop de eventos, reduzindo a eficiência da sua aplicação. Já as assíncronas aproveitam todo o potencial do asyncio, permitindo um uso mais eficiente dos recursos do sistema.
3. Tratamento de exceções
Erros em código assíncrono podem ser difíceis de rastrear, mas com blocos
try...except
, você pode lidar com falhas de forma controlada.Exemplo:
import asyncio import aiohttp async def buscar_dados(session, url): try: async with session.get(url) as resposta: resposta.raise_for_status() # Levanta erro HTTP, se houver return await resposta.text() except aiohttp.ClientError as e: print(f"Erro ao acessar {url}: {e}") return None async def main(): async with aiohttp.ClientSession() as session: dados = await buscar_dados(session, "https://www.naoexiste.com") print(dados) asyncio.run(main())
Neste exemplo, usamosraise_for_status()
para capturar erros HTTP e tratamos a exceção de forma que a aplicação não seja interrompida.
4. Gerenciamento de tempo limite (timeouts)
Em operações de I/O, como requisições de rede, definir um tempo limite é essencial para evitar que o programa fique preso esperando uma resposta indefinidamente.
Exemplo com Timeout:
import asyncio import aiohttp async def buscar_dados(session, url): try: async with session.get(url, timeout=5) as resposta: return await resposta.text() except asyncio.TimeoutError: print(f"Timeout ao acessar {url}") return None async def main(): async with aiohttp.ClientSession() as session: dados = await buscar_dados(session, "https://www.exemplo.com") print(dados) asyncio.run(main())
Aqui, o parâmetrotimeout=5
limita o tempo de espera a 5 segundos, melhorando a confiabilidade do programa.
5. Testes e monitoramento
Depurar e monitorar aplicações assíncronas pode ser desafiador. Use estas práticas para facilitar o processo:
- Logs detalhados: registre o fluxo de execução para identificar problemas.
- Ferramentas de profiling: analise onde o tempo está sendo gasto nas operações.
- Uso do
asyncio.gather()
: execute múltiplas corrotinas concorrentemente e receba os resultados em uma lista.
- Depuradores: familiarize-se com ferramentas como
pdb
ou depuradores IDE que suportem asyncio.
Exemplo de Logs:
import asyncio import logging logging.basicConfig(level=logging.INFO) async def tarefa(nome, tempo): logging.info(f"{nome} começou") await asyncio.sleep(tempo) logging.info(f"{nome} terminou") async def main(): await asyncio.gather( tarefa("Tarefa 1", 2), tarefa("Tarefa 2", 1) ) asyncio.run(main())
Saída:
INFO:root:Tarefa 1 começou INFO:root:Tarefa 2 começou INFO:root:Tarefa 2 terminou INFO:root:Tarefa 1 terminou
Use o asyncio.gather com cuidado
Embora
gather
seja uma ferramenta poderosa para executar tarefas em paralelo, ele interrompe todas as tarefas caso uma delas falhe. Certifique-se de tratar exceções individualmente se necessário.Exemplo para Isolar Exceções:
async def tarefa(nome, falhar=False): if falhar: raise ValueError(f"Erro na {nome}") return f"{nome} concluída" async def main(): tarefas = [ tarefa("Tarefa 1"), tarefa("Tarefa 2", falhar=True), tarefa("Tarefa 3") ] resultados = await asyncio.gather(*tarefas, return_exceptions=True) print(resultados) asyncio.run(main())
Saída:
['Tarefa 1 concluída', ValueError('Erro na Tarefa 2'), 'Tarefa 3 concluída']
Seguindo essas dicas e práticas, você estará bem preparado para desenvolver aplicações assíncronas robustas, eficientes e escaláveis com asyncio no Python!
Conclusão
Agora que você já sabe como o asyncio pode transformar o desempenho das suas aplicações Python, que tal colocar esse conhecimento em prática e dar o próximo passo na sua jornada como dev?
No curso Introdução ao Python com Flask, você desenvolverá uma API completa que simula um sistema de e-commerce. Aprenda a criar rotas, integrar bancos de dados, implementar autenticação de usuários e muito mais. Esse projeto prático é perfeito para construir uma base sólida em desenvolvimento web com Python e Flask.
Domine o universo Python com a nossa Formação Python. Com mais de 100 horas de conteúdo prático, você aprenderá a criar aplicações profissionais, dominar tecnologias como Flask, PyTest, WebSocket e AWS, e ainda contará com suporte personalizado, tutorias individuais e eventos exclusivos como o Talent Space. Prepare-se para construir um portfólio de tirar o fôlego e se destacar no mercado!
Não espere mais! Comece sua jornada com o curso gratuito e mergulhe de cabeça na Formação Python para se tornar o dev que o mercado procura. Vamos juntos?
Artigos_
Explore conteúdos relacionados
Descubra mais artigos que complementam seu aprendizado e expandem seu conhecimento.