Sistemas Síncronos e Assíncronos
Antes de iniciarmos nossos estudos, precisamos compreender alguns conceitos e como eles são relevantes no desenvolvimento de nossas soluções. Por diversas vezes, misturamos alguns deles, ou até utilizamos o nome de um quando estamos falando de outro. Por isso, vamos entender o que são sistemas síncronos e assíncronos.
Afinal, o que são sistemas síncronos e assíncronos?
0. TL;DR – Sistemas Síncronos vs. Assíncronos e AsyncIO em Python
Section titled “0. TL;DR – Sistemas Síncronos vs. Assíncronos e AsyncIO em Python”-
Sistemas Síncronos
- Executam tarefas de forma sequencial: cada passo só começa quando o anterior termina.
- Altamente previsíveis e fáceis de testar/depurar, ideais onde o tempo e a ordem exatos são críticos (ex.: controle industrial, transações).
- Podem desperdiçar recursos (bloqueios e espera ativa) e ter escalabilidade limitada.
-
Sistemas Assíncronos
- Permitem que várias operações ocorram “em paralelo” (no mesmo thread) sem bloquear umas às outras, usando eventos e callbacks.
- Melhor aproveitamento de CPU e I/O, elevada escalabilidade, recomendado em cenários de alto volume de requisições ou I/O intensivo.
- Mais complexos de gerenciar (estado, condições de corrida) e de depurar.
-
Como funciona internamente
- Um loop de eventos monitora uma fila de eventos: quando uma operação termina, dispara o callback associado.
- Difere de sistemas multithread: normalmente usa um único thread, evitando overhead de criação/sincronização de threads.
-
AsyncIO no Python
- Biblioteca padrão desde o Python 3.4, com sintaxe
async/await(oficialmente desde o 3.5). - Coroutines: funções marcadas com
async defque podem pausar sua execução emawait. - Tarefas: wrappers criados com
asyncio.create_task()para executar coroutines concorrentemente. asyncio.gather()easyncio.wait()agrupam múltiplas coroutines e esperam sua conclusão.
- Biblioteca padrão desde o Python 3.4, com sintaxe
-
Casos de uso práticos
- Operações de I/O (rede, disco, banco de dados) com drivers assíncronos (e.g.
aiohttp,aiosqlite,asyncpg). - Integração de código síncrono com
loop.run_in_executor()para não bloquear o evento. - Testes assíncronos com
pytest-asyncio.
- Operações de I/O (rede, disco, banco de dados) com drivers assíncronos (e.g.
-
Quando usar cada modelo
- Síncrono: quando a ordem e o tempo rígido de execução são fundamentais.
- Assíncrono: quando precisa de alta concorrência, baixa latência e melhor uso de recursos.
1. Sistemas Síncronos
Section titled “1. Sistemas Síncronos”Sistemas síncronos são aqueles em que as operações ocorrem em resposta direta a um estímulo e com requisitos estritos de tempo. Em uma execução síncrona, cada passo ou tarefa aguarda a conclusão da anterior antes de começar, e muitas vezes isso é controlado por sincronização explícita, como semáforos ou bloqueios. Isso garante que as operações ocorram de forma sequencial e ordenada.
Aqui estão alguns aspectos importantes dos sistemas síncronos:
- Coordenação de tempo: Em sistemas síncronos, as operações dependem de sinais ou eventos que ocorrem em intervalos específicos e previsíveis. Isso significa que a comunicação entre diferentes componentes do sistema é feita de forma que todos os elementos “esperem” ou se sincronizem uns com os outros baseados em relógios ou cronômetros.
- Previsibilidade: Devido à necessidade de operações ocorrerem em tempos específicos, esses sistemas são altamente previsíveis, o que é crucial em aplicações onde a temporização é essencial, como sistemas de controle industrial, telecomunicações ou sistemas embutidos em tempo real.
- Bloqueios e espera ativa: Em muitos sistemas síncronos, um processo pode bloquear ou entrar em um estado de espera ativa até que um evento específico ocorra, o que pode incluir a chegada de dados ou um sinal de outro processo. Este mecanismo de sincronização ajuda a manter a ordem e a coordenação dentro do sistema.
- Facilidade de teste e depuração: A natureza previsível e ordenada dos sistemas síncronos facilita o teste e a depuração, pois o comportamento do sistema em diferentes momentos é mais fácil de ser previsto e analisado.
Embora haja vantagens, os sistemas síncronos podem ser menos eficientes em termos de uso de recursos, pois podem exigir que os processos esperem por eventos externos, o que pode levar a um desperdício de ciclos de CPU, especialmente se a espera ativa for utilizada.
Na figura abaixo, é possível visualizar um exemplo de sistema síncrono, onde as operações ocorrem em resposta a um estímulo e de forma sequencial. Uma operação aguarda a conclusão da anterior antes de começar.
2. Sistemas Assíncronos
Section titled “2. Sistemas Assíncronos”Os sistemas assíncronos, em contraste com os sistemas síncronos, não dependem de uma sincronização estrita de tempo entre as operações. Em vez disso, eles permitem que as operações ocorram de forma independente, sem que uma operação precise esperar diretamente pela conclusão de outra antes de iniciar. Isso confere uma flexibilidade considerável e pode melhorar a eficiência e a escalabilidade em muitos contextos.
Definição de programação assíncrona:
Uma forma de permitir que o programa possa executar mais de uma tarefa de uma única vez, ao invés de esperar uma tarefa terminar e iniciar a próxima. Todas as tarefas são executas em conjunto.
A programa síncrona, realiza uma tarefa por vez, o que pode resultar em uma condição bloqueante para execução de alguma tarefa ou função.
Nem tudo são maravilhas, existe um tradeoff a ser considerado. Ao trabalhar com programação concorrente, alguns problemas podem acontecer:
- Race Conditions;
- Deadlocks;
- Resources Starvation (quando múltiplos processos ou threads tentam acessar o mesmo conjunto de registros);
Esses problemas ocorrem principalmente quando a sincronização não está propriamente configurada. A Programação Assíncrona permite utilizar uma forma estruturada e controlada para trabalhar com concorrência. Utilizando uma arquitetura orientada a eventos e um controle explicito sobre o fluxo de execução do programa.
Benefícios da programação Assíncrona:
- Permite a execução concorrente de tarefas;
- Oferece um melhor aproveitamento de desempenho e utilização de recursos;
- Resolve os problemas de execução concorrente utilizando a aproximação de evento;
- É bastante útil em cenários que envolve a execução longa de uma requisição ou operações de I/O custosas.
Na figura abaixo, é possível visualizar um exemplo de sistema assíncrono, onde as operações ocorrem de forma independente e não sequencial. Uma operação pode iniciar antes da conclusão da anterior. Vale destacar que isso é alcançado por meio de mecanismos de controle de fluxo e eventos.
Programação Assíncrona é melhor que Síncrona?

Podemos deixar quase a mesma resposta que a da pergunta anterior, variando as características do sistema. Lembrem-se sempre que a escolha entre sistemas síncronos e assíncronos depende amplamente das necessidades específicas e do contexto de cada aplicação. Ambos os tipos de sistemas têm suas vantagens e desvantagens, e a adequação depende do que é necessário para o sistema em questão.
-
Características dos Sistemas Assíncronos:
- Independência Temporal: As tarefas ou operações em um sistema assíncrono são executadas sem a necessidade de se alinharem a um relógio central ou esperar por um evento específico. Isso permite que as tarefas sejam processadas assim que os recursos estão disponíveis ou quando um evento relevante ocorre.
- Uso Eficiente de Recursos: Em sistemas assíncronos, os recursos não ficam ociosos esperando a conclusão de outras tarefas. Isso pode levar a uma utilização de recursos mais eficiente e a uma resposta mais rápida do sistema, especialmente em ambientes de alta carga.
- Escalabilidade: Sem a necessidade de processamento sequencial e sincronizado, sistemas assíncronos são frequentemente mais fáceis de escalar. Eles podem lidar com um aumento no volume de operações ou usuários mais facilmente, distribuindo tarefas de forma mais flexível.
- Complexidade de Gerenciamento de Estado: Embora ofereçam muitas vantagens, os sistemas assíncronos podem introduzir complexidade adicional no gerenciamento de estado, uma vez que o estado do sistema pode mudar em momentos inesperados, e o controle de dependências entre tarefas pode ser desafiador.
- Desafios de Depuração: A depuração e o teste podem ser mais complexos em sistemas assíncronos devido à natureza imprevisível e não sequencial das operações. Rastrear problemas específicos ou condições de corrida pode ser difícil sem ferramentas e técnicas adequadas.
-
Quando usar sistemas assíncronos:
- Em Aplicações de Alto Volume: Como serviços web ou sistemas de processamento de mensagens onde a capacidade de lidar com muitas tarefas ou solicitações de forma independente é crucial.
- Em Operações de I/O Intensivo: Sistemas que envolvem operações de entrada/saída intensivas, como acesso a redes ou a discos, podem beneficiar-se do modelo assíncrono para evitar bloqueios e aumentar a resposta do sistema.
- Em Ambientes Distribuídos: Em sistemas distribuídos, especialmente em computação em nuvem, onde os componentes podem estar fisicamente separados e as latências variáveis, a assincronicidade pode ajudar a melhorar a eficiência geral.
-
Quando evitar sistemas assíncronos:
- Quando a Ordem Exata é Crítica: Em sistemas onde a sequência precisa de operações é fundamental para a integridade dos dados ou para a lógica do negócio, a abordagem assíncrona pode introduzir riscos inaceitáveis.
- Em Ambientes com Requisitos de Tempo Real Estritos: Em aplicações onde o cumprimento de prazos precisos é crucial, a natureza imprevisível dos sistemas assíncronos pode ser um problema.
3. Como os Sistemas Assíncronos Funcionam?
Section titled “3. Como os Sistemas Assíncronos Funcionam?”Vamos analisar a imagem a seguir e entender como os sistemas assíncronos funcionam:
Sistemas assíncronos são baseados em eventos e callbacks. Em vez de esperar que uma operação seja concluída antes de iniciar outra, o sistema pode iniciar várias operações simultaneamente e lidar com os resultados à medida que eles se tornam disponíveis. Isso é frequentemente feito por meio de callbacks, que são funções que são chamadas quando uma operação assíncrona é concluída.
Mas Murilo, quem é que chama a função de callback? Quem lida com os eventos?
Em sistemas assíncronos, um loop de eventos é frequentemente usado para gerenciar a execução de tarefas e a chamada de callbacks. O loop de eventos é responsável por monitorar a fila de eventos e executar as funções de callback associadas a esses eventos. Isso permite que o sistema seja altamente responsivo e eficiente, pois pode lidar com várias operações concorrentes sem bloquear o thread principal.
Podemos pensar no loop de eventos como um gerenciador de fila que processa eventos à medida que eles chegam. Quando uma operação assíncrona é concluída, um evento é gerado e colocado na fila de eventos. O loop de eventos verifica continuamente a fila e executa as funções de callback associadas a esses eventos.
Ahhhh entendi Murilo! Então é como se eu tivesse rodando várias threads ao mesmo tempo né? (AQUI EU FORCEI A BARRA NA PERGUNTA MAS É PARA VERMOS UMA DIFERENÇA IMPORTANTE)
Então, não.
Aqui está uma diferença importante entre sistemas assíncronos e sistemas baseados em threads. Em sistemas baseados em threads, cada thread tem seu próprio contexto de execução e pode executar operações de forma independente. No entanto, o uso excessivo de threads pode levar a problemas de concorrência, como condições de corrida e dead locks. Quando estamos falando de sistemas assíncronos, geralmente estamos falando de um único thread que gerencia várias operações assíncronas, o que pode ser mais eficiente e seguro em muitos casos. O tempo de execução é compartilhado entre as operações, e o loop de eventos garante que as operações sejam executadas de forma ordenada e eficiente.
A figura abaixo demonstra uma comparação entre sistemas baseados em threads e sistemas assíncronos:
Com o Python, podemos utilizar a biblioteca asyncio para trabalhar com programação assíncrona. A biblioteca asyncio fornece uma estrutura para escrever código assíncrono usando a sintaxe async e await. Isso permite que você escreva código que pode lidar com operações assíncronas de forma eficiente e concorrente.
4. Python AsyncIO
Section titled “4. Python AsyncIO”Legal, agora temos o conceito alinhado conosco, vamos falar sobre o AsyncIO do Python.
Ele é uma biblioteca padrão do Python que fornece suporte para escrever código assíncrono usando a sintaxe async/await. Foi introduzido na versão 3.4 do Python e é uma das maneiras mais fáceis de escrever código assíncrono.
Para os próximos exemplos, vamos discutir alguns conceitos de utilização do AsyncIO. Ele, por padrão vem na biblioteca padrão do Python, então não é necessário instalar nada. CONTUDO, é uma boa prática fazer a separação de um ambiente virtual para cada projeto, então vamos criar um ambiente virtual para o nosso projeto.
python3 -m venv envsource env/bin/activateAo longo dos exemplos, vamos precisar de bibliotecas adicionais, ai elas podem ser adicionadas neste ambiente virtual, mantendo nossa instalação limpa.
5. Primeiro Código com AsyncIO
Section titled “5. Primeiro Código com AsyncIO”Primeiro programa:
# O asyncio é uma biblioteca que permite a execução de tarefas assíncronas# Ela é uma biblioteca padrão do Python 3.5 ou superiorimport asyncio
# Para marcarmos uma função como assíncrona, utilizamos a palavra-chave asyncasync def print_com_delay(tempo, mensagem): # A função asyncio.sleep é uma função assíncrona que suspende a execução da função por um determinado tempo await asyncio.sleep(tempo) print(mensagem)
# Agora configuramos uma função principal que irá chamar a função assíncronaasync def main(): # Adicionando um marcador para ver o tempo de execução da função print("Início da execução:", asyncio.get_event_loop().time()) # Realiza uma chamada bloqueante para a função assíncrona await print_com_delay(1, "Olá") await print_com_delay(2, "Mundo") # Adicionando um marcador para ver o tempo de execução da função print("Fim da execução:", asyncio.get_event_loop().time())
# Para chamar a função principal, utilizamos o método que cria um evento de loop e executa a função principalasyncio.run(main())Quanto utilizamos a palavra chave async estamos definindo que aquela função é especial e pode ser iniciada e pausada pelo event-loop do sistema. Quando utilizamos o operador await estamos esperando que a operação assíncrona termine para que a execução do código possa avançar. É uma chamada do tipo bloqueante.
A chamada asyncio.run(main()) é o entry-point de chamada para o nosso sistema. É ele que permite nossa execução assíncrona. O elemento fundamental para a execução do código assíncrono é o event-loop. Ele é fundamental para a coordenar a execução das tarefas sem bloquear o fluxo de execução principal do programa.
O event-loop é uma estrutura que continuamente monitora e processa eventos de diferentes fontes, como entradas, chamadas de rede ou mesmo eventos de temporizadores. Ele é o responsável por acionar os handlers ou callbacks específicos de cada evento. A chamada asyncio.run é responsável por fazer a criação do event-loop e executar a coroutine principal.
São responsabilidades do event-loop:
- Agendar e executar as tarefas assíncronas (corrotinas)
- Lidar com operações de I/O
- Lidar com temporizações e timeouts
- Liberar eventos para a fonte correspondente (handler)
Outro exemplo de utilização:
import asyncio
async def task(nome, tempo_delay): print(f"Task {nome} iniciada") await asyncio.sleep(tempo_delay) print(f"Task {nome} finalizada") return f"Task {nome} finalizada"
async def main(): # Cria uma lista de tarefas tasks = [ task("A", 1), task("B", 5), task("C", 3) ]
# Envia todas as tarefas para execução # O método gather() aguarda todas as tarefas serem finalizadas # Ele também lança a execução de todas as tarefas results = await asyncio.gather(*tasks) print(results)
# Cria um novo evento de loopasyncio.run(main())6. Síntaxe Async/Await
Section titled “6. Síntaxe Async/Await”O async/await foi adicionado no Python 3.5.
As coroutines são funções especiais que podem ter sua execução suspensa e resumida, possibilitando sua execução concorrente.
A palavra chave await serve para aguarda o fim da execução de uma operação assíncrona. Quando desejamos aguardar a execução de mais de uma coroutine de forma simultânea, nós devemos utilizar o asyncio.gather().
A partir da versão 3.8 do Python, o conceito de Native Coroutine foi implementado. Elas utilizam uma versão dedicada do opcode do await, resultando em um desempenho melhor e e redução do overhead de execução do código. A sintaxe async/await permanece a mesma, mas a sua implementação foi melhorada.
As Async Comprehensions permitem utilizar o recurso de list comprehensions para criar listas utilizando o retorno de coroutines. O mesmo pode ser utilizado com a criação de dicionários e sets.
# Utiliza o recurso de list comprehension para criar uma lista de tarefas utilizando um gerado assíncronoimport asyncio
async def quadrado_assincrono(n): await asyncio.sleep(1) return n * n
async def main(): numeros = [1, 2, 4, 8, 16, 32] numeros_quadrados = [await quadrado_assincrono(numero) for numero in numeros] print(numeros_quadrados)
# Versão otimizada com o método gather numeros_quadrados = await asyncio.gather(*[quadrado_assincrono(numero) for numero in numeros]) print(numeros_quadrados)
if __name__ == "__main__": asyncio.run(main())Chamando coroutine simples:
# Agendando e executando uma corrotina simples
import asyncio
async def corrotina(nome, delay): print(f"Corrotina {nome} iniciada - {asyncio.get_event_loop().time()}") await asyncio.sleep(delay) print(f"Corrotina {nome} finalizada - {asyncio.get_event_loop().time()}")
async def main(): # Prepara as corrotinas utilizando o método create_task # O método create_task() cria uma tarefa para execução de uma corrotina, mas não a executa. A execução é feita pelo loop de eventos. corrotina1 = asyncio.create_task(corrotina("A", 1)) corrotina2 = asyncio.create_task(corrotina("B", 2))
# Aguarda a execução das corrotinas, mas não bloqueia a execução await corrotina1 await corrotina2
# Cria um novo evento de loopif __name__ == "__main__": asyncio.run(main())IMPORTANTE: existe uma diferença entre chamar e aguardar uma corrotina e criar uma task com ela utilizando o
asyncio.create_task(). Quando apenas chamamos as corrotinas, elas são executadas conforme a execução dos pontos anteriores do programa vai terminando (o await fica com um comportamentobloqueante). Quando invocamos elas criando corrotinas (não apenas as funções assíncronas puras), oawaitnão é bloqueante.
# Agendando e executando uma coroutine simples
import asyncio
async def corrotina(nome, delay): print(f"corrotina {nome} iniciada - {asyncio.get_event_loop().time()}") await asyncio.sleep(delay) print(f"corrotina {nome} finalizada - {asyncio.get_event_loop().time()}")
async def main(): # Prepara as corrotinas utilizando o método create_task # O método create_task() cria uma tarefa para execução de uma corrotina, mas não a executa. A execução é feita pelo loop de eventos. corrotina1 = asyncio.create_task(corrotina("A", 1)) corrotina2 = asyncio.create_task(corrotina("B", 2))
# Aguarda a execução das corrotinas, mas não bloqueia a execução await corrotina1 await corrotina2
# Chamada do exemplo anterior, mas sem a criação de tarefas corrotina3 = corrotina("C", 3) corrotina4 = corrotina("D", 4) await corrotina3 await corrotina4
# Cria um novo evento de loopif __name__ == "__main__": asyncio.run(main())Saída da execução do código:
> python corrotina-simples.pycorrotina A iniciada - 428855.359corrotina B iniciada - 428855.359corrotina A finalizada - 428856.359corrotina B finalizada - 428857.375corrotina C iniciada - 428857.375corrotina C finalizada - 428860.375corrotina D iniciada - 428860.375corrotina D finalizada - 428864.39Podemos criar nosso próprio event-loop e controlar sua execução:
# Programa para lidar com a execução de multiplas corrotinasimport asyncio
async def corrotina(nome_arquivo): print(f"corrotina {nome_arquivo} iniciada - {asyncio.get_event_loop().time()}") await asyncio.sleep(2) print(f"corrotina {nome_arquivo} finalizada - {asyncio.get_event_loop().time()}") return f"corrotina {nome_arquivo} finalizada"
async def main(): # Cria uma lista com os nomes dos arquivos arquivos = ["arquivo1.txt", "arquivo2.txt", "arquivo3.txt", "arquivo4.txt"] chamada_download = [corrotina(arquivo) for arquivo in arquivos] # # Esse método está deprecado, mas ainda é utilizado para aguardar a execução de todas as corrotinas # # Utiliza o método wait() para aguardar a execução de todas as corrotinas # finalizada, pendente = await asyncio.wait(chamada_download, return_when=asyncio.ALL_COMPLETED)
# # Exibe o resultado da execução # for corrotina_finalizada in finalizada: # print(corrotina_finalizada.result())
# Versão não deprecada # Utiliza o método gather() para aguardar a execução de todas as corrotinas resultados = await asyncio.gather(*chamada_download) for resultado in resultados: print(resultado)
if __name__ == "__main__": asyncio.run(main())7. Gerenciamento de Tasks com AsyncIO
Section titled “7. Gerenciamento de Tasks com AsyncIO”As Tasks são os blocos de construção para executar e gerenciar as operações utilizando o AsyncIO. As Tasks são um wrapper adicionado nas corrotinas para trazer mais funcionalidades de controle a elas.
# Lidando com o gerenciamento de tasks no Python com Asyncio
import asyncio
# Cria uma rotinaasync def rotina(delay): print(f"Rotina iniciada - {asyncio.get_event_loop().time()}") await asyncio.sleep(delay) print(f"Rotina finalizada - {asyncio.get_event_loop().time()}") return f"Rotina finalizada"
# Cria a função principalasync def main(): # Cria duas tarefas para serem executadas tarefa1 = asyncio.create_task(rotina(1)) tarefa2 = asyncio.create_task(rotina(2))
await asyncio.sleep(1)
# Cancela a execução da tarefa 2 tarefa2.cancel()
# Aguarda a execução das tarefas await asyncio.gather(tarefa1, tarefa2, return_exceptions=True)
# Cria o evento de loopif __name__ == "__main__": asyncio.run(main())É possível verificar o estado de uma task. É importante notar algumas coisas:
- Erros e exceções nas corrotinas devem ser tratados dentro do event loop.
- Se o result() de uma task for acessado antes dela terminar, vai resultar no lançamento de uma exceção.
# Verifica o estado de uma task
import asyncio
async def corrotina1(nome): await asyncio.sleep(3) return nome
async def corrotina2(nome): # corrotina que lança uma exceção await asyncio.sleep(5) raise ValueError("Erro na corrotina") return nome
# Função principalasync def main(): try: # Cria as tasks task1 = asyncio.create_task(corrotina1("A")) task2 = asyncio.create_task(corrotina2("B"))
# Aguarda a execução das tasks await asyncio.sleep(1)
# Verifica o estado das tasks - verificando se elas estão terminadas print(task1.done()) print(task2.done())
# Exibe o estado atual das tasks print(task1._state) print(task2._state)
# Aguarda a execução das tasks await asyncio.gather(task1, task2, return_exceptions=True)
# Verifica o estado das tasks print(task1.done()) print(task2.done())
# Exibe o resultado das tasks print(task1.result()) print(task2.result()) except ValueError as e: print(f"Erro: {e}")
# Cria um novo evento de loopif __name__ == "__main__": asyncio.run(main())Uma comparação de tempo interessante:
import asyncio
async def compute(): return sum(i * i for i in range (10000000))
async def main(): print(asyncio.get_event_loop().time()) result1 = await compute() print(asyncio.get_event_loop().time()) result2 = sum(i * i for i in range (10000000)) print(asyncio.get_event_loop().time())
print(result1, result2)
if __name__ == "__main__": asyncio.run(main())8. Trabalhando com AsyncIO para Requisições de Rede
Section titled “8. Trabalhando com AsyncIO para Requisições de Rede”Utilizando os conceitos presentes no AsyncIO, é possível construir aplicações cliente-servidor que conseguem lidar com uma quantidade muito mais elevada de requisições, melhorando a utilização dos recursos disponíveis. O AsyncIO aceita conexões seguras de SSL/TLS. Exemplo de servidor assíncrono:
# Construção de um servidor assincrono utilizando apenas o Asyncioimport asyncio
# Definição do método que será chamado para lidar com clientesasync def handle_client(reader, writer): # Os elementos reader e writer são objetos que permitem a leitura e escrita de dados # O método read() é utilizado para ler dados do cliente # O valor informado é a quantidade de bytes que serão lidos data = await reader.read(1024) message = data.decode() print(f"Dados Recebido: {message}")
resposta = "Ola Cliente!" writer.write(resposta.encode()) # O método drain() é utilizado para garantir que todos os dados foram escritos await writer.drain() print("Fechando Conexão") writer.close() await writer.wait_closed()
# Criação do servidorasync def main(): # Cria uma instância de servidor que escuta na porta 8888 server = await asyncio.start_server( handle_client, 'localhost', 8888)
# Cria um loop para aguardar a conexão de clientes # A criação com o método async with garante que o servidor será fechado corretamente async with server: # Inicia o servidor await server.serve_forever()
# Inicia o servidorif __name__ == "__main__": asyncio.run(main())Exemplo de um cliente para conectar nesse servidor assíncrono:
import asyncio
async def connect_to_server(): reader, writer = await asyncio.open_connection('localhost', 8888) message = "oi tudo bem ai?" writer.write(message.encode()) await writer.drain() data = await reader.read(1024) print(f"Resposta do servidor: {data.decode()}") writer.close() await writer.wait_closed()
if __name__ == "__main__": asyncio.run(connect_to_server())É possível utilizar o AsyncIO para manipular bases de dados também. Utilizando drivers assíncronos, é possível realizar consultas e chamadas as bases de dados de forma concorrente. Alguns drivers assíncronos:
- AsyncPG (for PostgreSQL)
- Motor (for MongoDB)
- Aio_pika (for RabbitMQ)
- Asyncio Redis (for Redis)
- aiosqlite (for sqlite)
Exemplo de execução de código utilizando o aisqlite. Primeiro é necessário instalar a dependencia.
python -m pip install aiosqliteE o código para manipular os dados:
import asyncioimport aiosqlite
# Cria uma corrotina para criar as tabelasasync def criar_tabelas(db_name, table_name): async with aiosqlite.connect(db_name) as db: await db.execute(f"CREATE TABLE IF NOT EXISTS {table_name} (id INTEGER PRIMARY KEY, mensagem TEXT)") await db.commit()
# Insere alguns dados aleatórios em uma tabelaasync def inserir_dados(db_name, table_name): async with aiosqlite.connect(db_name) as db: for i in range(10): await db.execute(f"INSERT INTO {table_name} (mensagem) VALUES ('Mensagem {i}')") await db.commit()
# Pega dados que estão na tabelaasync def pegar_dados(db_name, table_name): async with aiosqlite.connect(db_name) as db: async with db.execute(f"SELECT * FROM {table_name}") as cursor: return [row async for row in cursor]
# Função principalasync def main(): db_name = "banco.db" table_name = "mensagens" await criar_tabelas(db_name, table_name) await inserir_dados(db_name, table_name) dados = await pegar_dados(db_name, table_name) print(dados)
# Roda a função principalif __name__ == "__main__": asyncio.run(main())Em algumas situações, vai ser necessário utilizar código assíncrono com código síncrono. É possível realizar a troca de informações entre estes dois sistemas. Esse comportamento é especialmente necessário quando algumas bibliotecas ou trechos do código que serão utilizados são síncronos.
Primeiro instalando a dependência da biblioteca requests:
python -m pip install requests# Exemplo de utilização de código sincrono em conjunto com código assincronoimport asyncioimport requests
# Cria uma corrotina que fica como uma ponte entre código assincrono e código sincronoasync def fetch_url(url): # Pega o loop de eventos loop = asyncio.get_event_loop() # Roda a função requests.get de forma sincrona, mas em uma thread separada return await loop.run_in_executor(None, requests.get, url)
# Função principalasync def main(): url = "https://www.google.com" # Os parênteses são necessários para pegar o texto da resposta. # Eles garantem que a função fetch_url seja chamada primeiro. data = (await fetch_url(url)).text print(data)
# Roda a função principalif __name__ == "__main__": asyncio.run(main())Utilizar o método run_in_executor previne que o event-looppossa ser bloqueado durante a execução do código.
9. Aplicando Testes com AsyncIO
Section titled “9. Aplicando Testes com AsyncIO”É possível escrever código de testes para as aplicações assíncronas também. Para tal, vai ser necessário utilizar as bibliotecas pytest e pytest-asyncio.
python -m pip install pytest pytest-asyncioO código da função:
# Exemplo de teste de função assíncrona com pytestimport asyncio
async def fetch_data(): await asyncio.sleep(2) return 'data'O código do teste:
# Cria o teste para a função fetch_dataimport pytestfrom exemplo_teste_assincrono import fetch_data
# Marca a função como de teste e com comportamento assincrono@pytest.mark.asyncioasync def test_fetch_data(): data = await fetch_data() assert data == 'data', "Resultado esperado não foi retornado."Para executar os códigos de teste:
python -m pytest teste-exemplo-teste-assincrono.py10. Projetos Assíncronos
Section titled “10. Projetos Assíncronos”Para trabalhar com requisições HTTP de forma assíncrona, uma biblioteca que pode ser utilizada é a aiohttp. Ela pode ser instalada com:
python -m pip install aiohttpE um exemplo de código:
import asyncioimport aiohttp
# Cria a função que irá fazer a requisiçãoasync def fetch( url): # Cria uma sessão. Ela representa uma conexão com o servidor async with aiohttp.ClientSession() as session: # Faz a requisição async with session.get(url) as response: # Lê o conteúdo da resposta # Neste exemplo, o conteúdo está sendo retornado como texto return await response.text()
# Cria a função principalasync def main(): url = 'https://www.uol.com.br' html = await fetch(url) print(html)
# Cria o event-loopif __name__ == '__main__': asyncio.run(main())É possível realizar um conjunto de requisições de forma assíncrona utilizando a biblioteca.
import asyncioimport aiohttp
# Cria a função que irá fazer a requisiçãoasync def fetch( url): # Cria uma sessão. Ela representa uma conexão com o servidor async with aiohttp.ClientSession() as session: # Faz a requisição async with session.get(url) as response: # Lê o conteúdo da resposta # Neste exemplo, o conteúdo está sendo retornado como texto return await response.text()
# Cria a função principalasync def main(): urls = ['https://www.uol.com.br', 'https://www.globo.com', 'https://www.terra.com.br'] # Cria uma lista de tarefas tasks = [asyncio.create_task(fetch(url)) for url in urls] html = await asyncio.gather(*tasks) print(html)
# Cria o event-loopif __name__ == '__main__': asyncio.run(main())É possível adicionar um controle de número de retentativas que a função vai fazer se não for possível receber os dados de resposta quando uma requisição é realizada.
import asyncioimport aiohttp
# Cria a função que irá fazer a requisiçãoasync def fetch( url, max_tries=3): # Tenta fazer a requisição até 3 vezes tentativa = 0 while tentativa < max_tries: try: # Cria uma sessão. Ela representa uma conexão com o servidor async with aiohttp.ClientSession() as session: # Faz a requisição async with session.get(url) as response: # Lê o conteúdo da resposta # Neste exemplo, o conteúdo está sendo retornado como texto return await response.text() except aiohttp.ClientError as e: tentativa += 1 # Espera 1 segundo antes de tentar novamente await asyncio.sleep(1) return None
# Cria a função principalasync def main(): urls = ['https://www.uol.com.br', 'https://ww.globo.com', 'https://www.terra.com.br'] # Cria uma lista de tarefas tasks = [asyncio.create_task(fetch(url)) for url in urls] htmls = await asyncio.gather(*tasks) # Verifica os retornos for html in htmls: if html: print('Resultado obtido com sucesso') else: print('Erro ao acessar a URL')
# Cria o event-loopif __name__ == '__main__': asyncio.run(main())11. Para saber mais
Section titled “11. Para saber mais”Pessoal vou deixar aqui mais alguns vídeos que eu recomendo para compreender a forma como os sistemas assíncronos evoluíram no Python. Eles são densos, recomendo assistir eles na ordem qu eu vou deixar aqui (menos denso para mais denso):
- Asyncio in Python - Full Tutorial (tem propaganda no meio do vídeo, mas é um bom tutorial)
- Live de Python #154 - Uma introdução histórica à corrotinas PARTE 3 (AsyncIO):
- Petr Viktorin: Building an async event loop