Garanta sua assinatura com até 35%OFF antes do reajuste. Quero aproveitar

Asyncio no Python: eficiência no seu código
Rocketseat

Rocketseat

5 min de leitura
python
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, usamos raise_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âmetro timeout=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.

Aprenda programação do zero e DE GRAÇA

No Discover você vai descomplicar a programação, aprender a criar seu primeiro site com a mão na massa e iniciar sua transição de carreira.

COMECE A ESTUDAR AGORA