
Desmistificando o Async em Python: Um Guia para Automação de Redes
5 min read
A programação assíncrona (async/await) em Python é uma das funcionalidades mais poderosas e, ao mesmo tempo, mais mal compreendidas. Para engenheiros de automação de redes, cujas tarefas são dominadas por esperas: esperar por uma conexão SSH, pela resposta de uma API, pelo output de um comando, o async é uma necessidade. Ele promete executar dezenas de tarefas de rede no tempo que levaria para fazer apenas uma.
No entanto, entrar no mundo async pode ser confuso. O que é uma corrotina? Eu preciso mesmo do asyncio? E, a pergunta mais crítica de todas: o que acontece se eu misturar código síncrono e assíncrono?
O Básico: Funções Síncronas vs. Assíncronas
Função Síncrona (normal): Quando você a chama, ela executa do início ao fim sem interrupção. Se ela precisa esperar por algo (como uma resposta de rede), todo o seu programa para e espera junto. É como um telefonema: você fica na linha até a conversa acabar.
Função Assíncrona (Corrotina): É uma função que pode ser pausada e retomada. Quando ela encontra uma operação de espera (marcada com
await), ela diz ao Python: "Ei, isso vai demorar. Pode ir fazendo outra coisa enquanto eu espero". É como enviar uma mensagem de texto: você envia e pode fazer outras coisas até a resposta chegar.
As Peças do Quebra-Cabeça Async
Para o async funcionar, precisamos de três componentes principais:
Corrotinas (
async def): São as funções "pausáveis". Você as define comasync def.await: A palavra-chave que de fato pausa a corrotina e devolve o controle, dizendo "estou esperando por este resultado". Você só pode usarawaitdentro de umaasync def.O Event Loop: É o maestro que orquestra tudo. Ele mantém uma lista de tarefas e executa uma de cada vez. Quando uma tarefa é pausada com
await, o event loop imediatamente pega a próxima tarefa da lista e a executa. Ele é o coração doasyncio.
Eu Preciso do asyncio?
Sim e não.
Não, você não precisa importar e interagir com todas as partes complexas do
asyncioo tempo todo.Sim, você precisa de algo para rodar o event loop e gerenciar as corrotinas. O
asyncioé a biblioteca padrão do Python para isso.
Na prática, seu uso do asyncio pode ser tão simples quanto:
asyncio.run(main()): O ponto de entrada que inicia o event loop e executa sua corrotina principalmain.asyncio.gather(*tasks): Uma forma de executar uma lista de corrotinas concorrentemente e esperar que todas terminem.
Você não precisa construir o event loop manualmente; o asyncio cuida da parte difícil para você.
O Pecado Capital: Chamar Código Bloqueante em uma Corrotina
Esta é a regra mais importante da programação assíncrona: nunca, jamais, chame uma função síncrona e bloqueante dentro de uma corrotina.
Funções bloqueantes são aquelas que fazem o programa esperar, como:
time.sleep(5)requests.get(url)Uma chamada de uma biblioteca de rede que não é
async, comonetmiko.ConnectHandler(...)
O que acontece quando você faz isso?
Você congela o chef de cozinha. A função bloqueante toma controle total do processo e não o devolve até que ela termine. O event loop fica paralisado, incapaz de trocar de tarefa. Todas as outras corrotinas que estavam prontas para rodar ficam esperando, famintas. Todo o benefício da concorrência é perdido.
sequenceDiagram
participant EventLoop
participant CorrotinaA
participant CorrotinaB
participant FuncaoSincrona
EventLoop->>CorrotinaA: Executar
CorrotinaA->>FuncaoSincrona: Chamar time.sleep(5)
Note over EventLoop,FuncaoSincrona: O EVENT LOOP ESTÁ BLOQUEADO!
Note over CorrotinaB: Esperando para executar...
FuncaoSincrona-->>CorrotinaA: Retorna após 5s
CorrotinaA-->>EventLoop: Finaliza
EventLoop->>CorrotinaB: Finalmente executa
No diagrama acima, a CorrotinaB só pôde executar depois que a FuncaoSincrona (o time.sleep(5)) liberou o processo, 5 segundos depois. A concorrência foi destruída.
A Solução: asyncio.to_thread()
Então, como usamos bibliotecas síncronas e bloqueantes (como a netmiko, que é essencial para muitos de nós) em um código async?
A resposta é delegar o trabalho bloqueante para um thread separado, liberando o event loop para continuar seu trabalho. Desde o Python 3.9, o asyncio tornou isso incrivelmente fácil com asyncio.to_thread().
asyncio.to_thread(func, *args) pega uma função síncrona func e seus argumentos *args, a executa em um thread separado e retorna um objeto "aguardável" (awaitable). O event loop pode "esperar" por esse objeto sem ser bloqueado.
Caso Prático: Netmiko (Síncrono) + HTTPX (Assíncrono)
Vamos criar um script que, concorrentemente, busca a configuração de um roteador usando netmiko e busca dados de uma API usando httpx.
O jeito ERRADO (bloqueante):
import asyncio
import httpx
from netmiko import ConnectHandler
async def get_config_bloqueante(device): # RUIM!
print('Iniciando conexão Netmiko...')
with ConnectHandler(**device) as conn:
output = conn.send_command('show run')
print('Conexão Netmiko finalizada.')
return output
async def get_api_data():
print('Iniciando chamada de API...')
async with httpx.AsyncClient() as client:
await client.get('https://httpbin.org/delay/2') # Simula espera de 2s
print('Chamada de API finalizada.')
async def main():
# ... (definição do dispositivo) ...
await asyncio.gather(
get_config_bloqueante(device), # Esta chamada irá bloquear tudo!
get_api_data()
)
# Se a conexão Netmiko levar 5s, a chamada de API só começará depois disso.
O jeito CERTO (com to_thread):
import asyncio
import httpx
from netmiko import ConnectHandler
# Função síncrona e bloqueante, como deve ser
def get_config_sincrono(device):
print('Iniciando conexão Netmiko em um thread...')
with ConnectHandler(**device) as conn:
output = conn.send_command('show run')
print('Conexão Netmiko finalizada.')
return output
async def get_api_data():
print('Iniciando chamada de API...')
async with httpx.AsyncClient() as client:
await client.get('https://httpbin.org/delay/2')
print('Chamada de API finalizada.')
async def main():
device = {
# dados do router
}
# Delega a função bloqueante para um thread separado
netmiko_task = asyncio.to_thread(get_config_sincrono, device)
api_task = get_api_data()
await asyncio.gather(netmiko_task, api_task)
asyncio.run(main())
Nesta versão, enquanto get_config_sincrono está rodando em seu próprio thread, o event loop está livre para executar get_api_data concorrentemente. As duas tarefas progridem ao mesmo tempo.
Conclusão
Entender async é entender a arte de não esperar. Para automação de redes, onde o tempo de espera é o maior inimigo, isso é transformador.
Lembre-se das regras de ouro:
Use
async/awaitpara tarefas I/O-bound: Interações com rede (APIs, SSH) são o caso de uso perfeito.Nunca bloqueie o Event Loop: Uma única chamada síncrona bloqueante em uma corrotina destrói toda a vantagem do
async.Use
asyncio.to_thread(): É a sua ponte segura para integrar bibliotecas síncronas e legadas em um mundo assíncrono moderno.
Ao dominar esses conceitos, você estará equipado para escrever automações de rede drasticamente mais rápidas, eficientes e escaláveis.
Referências:
