Boas-vindas! Meu nome é Laís Urano, sou instrutora na escola de Programação da Alura, e irei te acompanhar ao longo dessa jornada de aprendizagem.
Audiodescrição: Laís se descreve como uma mulher parda, de olhos castanho-claros, e cabelos cacheados, longos e castanho-escuros com mechas azuis. Ela usa cordões prateados no pescoço, batom vermelho nos lábios, delineado preto nos olhos, e está sentada em uma cadeira rosa em frente a uma parede clara iluminada em gradiente azul-escuro e verde-claro. À direita de Laís, há um microfone branco sobre um braço articulado preto.
Até o momento, durante a formação Praticando Python, trabalhamos com a programação síncrona, um modelo de execução em que tarefas são realizadas de forma sequencial, ou seja, cada operação deve ser concluída para que a próxima se inicie.
Abaixo, temos um exemplo que utiliza a biblioteca time
:
import time
def tarefa(numero):
print(f"Iniciando tarefa {numero}.")
time.sleep(2)
print(f"Tarefa {numero} concluída!")
tarefa(1)
tarefa(2)
tarefa(3)
No código, há uma função tarefa()
que recebe numero
como parâmetro. Nela, existem dois print()
: o primeiro indica o início da tarefa; o segundo, um time.sleep()
, ou seja, uma pausa de 2 segundos; e outro print()
indicando que a tarefa foi concluída.
Além disso, realizamos três chamadas de tarefa()
:
tarefa(1)
;tarefa(2)
;tarefa(3)
.
Quando executamos esse programa, iniciamos a tarefa 1, concluímos, depois iniciamos a tarefa 2, concluímos, e assim por diante. Ou seja, para que a tarefa 2 inicie, a tarefa 1 precisa ter sido iniciada e concluída. Da mesma forma, para que a tarefa 3 inicie, a tarefa 2 precisa também ter sido iniciada e concluída. Isso ocorre de forma sequencial, conforme dito anteriormente.
No entanto, esse modelo pode ser considerado inapropriado em alguns contextos.
Em contextos mais específicos, utilizamos a programação assíncrona, que permite executar várias tarefas ao mesmo tempo, sem que uma precise esperar a outra terminar para iniciar.
Nesse tipo de programação, temos duas possibilidades:
- A primeira é a concorrência, na qual alternamos tão rapidamente entre as tarefas executadas, que passamos a impressão de que elas rodam ao mesmo tempo. Isso ocorre, por exemplo, quando utilizamos tarefas de entrada e saída de dados, como chamadas a uma API ou acesso a um banco de dados;
- A segunda é o paralelismo, em que múltiplas tarefas são executadas simultaneamente. Isso exige que a máquina tenha uma potência superior para lidar com programas de cálculos pesados e processamento de dados.
Imagine o cenário de atendimento em um restaurante.
De forma síncrona, o garçom ou a garçonete atendem somente um cliente por vez, pegando o próximo pedido apenas quando o primeiro terminar. Ou seja, o primeiro cliente chega, faz o pedido, espera, come, paga, sai, e só então o segundo cliente é atendido.
Por outro lado, de forma assíncrona, essa pessoa anotaria o pedido do primeiro cliente e, enquanto a cozinha prepara esse pedido, ela atenderia outro cliente.
Em uma abordagem assíncrona, temos duas possibilidades:
- A concorrência, na qual um garçom atende vários clientes e alterna entre eles, mas continua sozinho no trabalho;
- Ou o paralelismo, em que vários garçons atendem diferentes clientes simultaneamente em mesas distintas no restaurante.
A programação síncrona deve ser usada quando o fluxo do programa for simples e sequencial.
Por exemplo: quando precisamos calcular o desconto de uma compra, ou quando a lógica do código depende da execução ordenada das operações. Imagine que acabamos de realizar uma compra em um site. Nesse cenário, é necessário que o pagamento seja confirmado antes do pedido ser enviado.
A síncrona também é adequada quando há pouca ou nenhuma espera por entrada e saída de dados (I/O), ou quando o código é puramente computacional e pesado. Além disso, utilizamos essa programação quando a simplicidade do código é mais importante que a velocidade, como ao criar um script simples de automação para renomear um arquivo.
Por outro lado, a programação assíncrona é indicada em contextos onde múltiplas tarefas dependem de I/O, como ao trabalhar com uma API externa ou consultar um banco de dados.
Também utilizamos a programação assíncrona quando precisamos lidar com várias conexões simultâneas, como em um chatbot que precisa interagir com vários clientes ao mesmo tempo, ou quando o sistema precisa melhorar o tempo de resposta.
Além disso, a programação assíncrona é útil quando as tarefas podem ser executadas paralelamente, sem depender umas das outras ou interferir entre si.
A escolha entre programação síncrona e assíncrona pode ser feita com base na lista de critérios mencionados ou por um senso crítico, considerando a pessoa desenvolvedora do projeto ou a empresa responsável pelo desenvolvimento.
Por exemplo: no envio de e-mails de confirmação, podemos optar por uma resposta assíncrona. Se nos cadastramos em um site e é solicitado um e-mail de confirmação, poderemos utilizar o sistema apenas após confirmar o e-mail. No entanto, se o acesso for assíncrono, o e-mail é enviado em segundo plano, permitindo utilizar o sistema.
No final, a escolha depende da pessoa desenvolvedora.
Agora que entendemos como funcionam e como podemos utilizar a programação síncrona e assíncrona, podemos aplicar esse conhecimento nos nossos projetos.
Faremos isso a seguir. Nos encontramos no próximo vídeo!
Agora que compreendemos a diferença entre programação síncrona e assíncrona, para aplicar a programação assíncrona efetivamente em nossos projetos, utilizaremos a biblioteca asyncio
, uma biblioteca padrão do Python para escrita de código assíncrono.
asyncio
Nesse caso, não precisamos fazer nenhuma instalação, apenas chamar a biblioteca.
A biblioteca asyncio
é utilizada para operações como chamadas de API sem bloquear o programa, leitura e escrita de arquivos ou bancos de dados, e criação de servidores e bots que lidam com conexões simultâneas. Todos esses casos estão inseridos no contexto de programação assíncrona.
Para começarmos a entender a programação assíncrona em nosso código, precisamos compreender certos termos. O primeiro deles é awaitables, ou aguardáveis, que são objetos que podem ser esperados com await
. O await
se traduz literalmente para "aguarde", existindo três tipos:
- Corrotinas;
- Tarefas;
- Futuros.
Abaixo, trouxemos o exemplo de um código que simula a execução de uma tarefa, com um print()
de "Início." e "Fim." dentro de uma função chamada corrotina()
.
import asyncio
async def corrotina():
print("Início.")
await asyncio.sleep(2)
print("Fim.")
asyncio.run(corrotina())
No escopo da função, utilizamos await asyncio.sleep(2)
. Fizemos a chamada da biblioteca asyncio
e usamos sleep()
, agora não mais da biblioteca time
, mas da asyncio
.
Esse sleep()
específico não irá parar o código por dois segundos, mas sim aguardar o código por dois segundos. Por fim, executamos a função corrotina()
.
O que é uma corrotina? Como ela funciona? A corrotina, ou coroutine, é uma função especial que pode ser pausada e retomada durante sua execução. A função corrotina()
é um aguardável e precisa do await
. Para definir uma corrotina, que seria basicamente uma função assíncrona, utilizamos o termo async
antes da palavra reservada def
, que define a função.
Com isso, não se trata mais de uma função comum, mas de uma corrotina, que assume esse nome e papel. Ao executar corrotina()
, recebemos as saídas "Início." e "Fim.". Para executar a corrotina, precisamos usar run()
, pois ela não é identificada como função comum, mas assíncrona.
Para executar a função corrotina()
ou qualquer outra função assíncrona, utilizamos o método run()
ou diretamente o await
. Observe o exemplo de código a seguir:
import asyncio
async def corrotina(numero):
print(f"Iniciando tarefa {numero}.")
await asyncio.sleep(2)
print(f"Tarefa {numero} concluída!")
async def main():
await corrotina(1)
await corrotina(2)
asyncio.run(main())
Nesse novo contexto, podemos simular a execução de uma tarefa definindo uma função main()
. Essa função aguardará a corrotina(1)
e a corrotina(2)
, chamando no método run()
a função main()
para executar todo o projeto.
Na função que simula a ação da tarefa, iniciamos a tarefa 1, concluímos a tarefa 1, iniciamos a tarefa 2 e concluímos a tarefa 2. Isso deveria ser uma concorrência, mas não acontece, pois dentro da função main()
, aguardamos separadamente duas corrotinas. Executamos primeiro uma ação, que é aguardar corrotina(1)
, e depois aguardamos corrotina(2)
.
Para que as corrotinas trabalhem simultaneamente com concorrência, precisamos utilizar as tasks, ou tarefas, que são objetos que executam a corrotina de forma concorrente, permitindo que várias sejam executadas juntas. Abaixo, temos um exemplo do uso de tarefas em código:
import asyncio
async def corrotina(numero):
print(f"Iniciando tarefa {numero}.")
await asyncio.sleep(2)
print(f"Tarefa {numero} concluída!")
async def main():
tarefa1 = asyncio.create_task(corrotina(1))
tarefa2 = asyncio.create_task(corrotina(2))
await tarefa1
await tarefa2
asyncio.run(main())
Para trabalhar com uma tarefa, podemos utilizar create_task()
da própria biblioteca asyncio
. No escopo de main()
, alteramos o código para criar uma tarefa utilizando asyncio.create_task()
.
Na primeira função create_task()
, associamos a tarefa à corrotina(1)
. Depois, criamos uma tarefa2
associada à corrotina(2)
, que será a mesma corrotina, mas com valores diferentes.
Em seguida, utilizamos o await
para executar não a corrotina, mas a tarefa, com await tarefa1
e depois await tarefa2
. Nesse momento, iniciamos as duas tarefas simultaneamente.
Como isso acontece quando entramos na corrotina? Primeiramente, iniciamos a tarefa1
, entramos e iniciamos a tarefa1
, e começamos a aguardar no sleep()
.
Enquanto aguardamos, verificamos se há outra tarefa que precisa ser executada, que neste caso é a tarefa2
. Assim, enquanto aguardamos a tarefa1
, iniciamos a tarefa2
simultaneamente. Como a tarefa1
foi iniciada primeiro, será concluída primeiro, pois as tarefas têm o mesmo tempo.
Existem casos na programação assíncrona em que precisamos lidar com valores chamados de futuros, ou futures. Futuros são valores indefinidos que se definem no futuro, ou seja, algo que ainda assumirá um valor desconhecido. Eles são utilizados na integração de APIs de baixo nível.
Para utilizar futuros em código, usamos asyncio.Future()
. No escopo, percebemos algumas alterações. Novamente, este projeto funcionará como uma simulação de tarefa:
import asyncio
async def corrotina(futuro):
print("Início.")
await asyncio.sleep(2)
futuro.set_result("Fim.")
async def main():
futuro = asyncio.Future()
asyncio.create_task(corrotina(futuro))
resultado = await futuro
print(resultado)
asyncio.run(main())
Na função main()
, definimos futuro
como uma variável. Esta variável recebe o método asyncio.Future()
. No momento em que criamos a tarefa (create_task()
) e passamos a corrotina()
, passamos também futuro
para corrotina()
.
Na função corrotina()
, há um print()
de "Início."; o await
, aguardável de async
; e o sleep()
. Depois, utilizamos futuro.set_result()
com o valor "Fim.", ou seja, executamos a tarefa e definimos o valor do futuro
. Feito isso, como já executamos a função assíncrona, que aguarda para finalizar, ela recebe o valor do futuro
na variável resultado
.
No momento em que imprimimos resultado
, ele define a resposta de futuro
. O primeiro print()
recebido na saída será o "Início.", que vem junto com a execução da função corrotina()
, ou seja, print("Início.")
. Enquanto aguardamos, ele só executará set_result("Fim.")
após informarmos o resultado do futuro
e passarmos esse resultado para a variável resultado
.
Para ilustrar melhor, vamos imaginar outro cenário:
import asyncio
async def corrotina1(futuro):
print("Tarefa 1 iniciada.")
await asyncio.sleep(2)
futuro.set_result("Resultado da Tarefa 1")
print("Tarefa 1 finalizada.")
async def corrotina2(futuro):
print("Tarefa 2 iniciada, aguardando o futuro.")
resultado = await futuro
print("Tarefa 2 finalizada com o resultado:", resultado)
# código omitido
No código acima, temos duas corrotinas em execução simultaneamente, em que a primeira executa certa tarefa, mas a segunda precisa do resultado da primeira. Nesse caso, iremos trabalhar em determinada operação, e quando tivermos o resultado dessa operação, passaremos para a segunda.
A corrotina1()
, por exemplo, inicia a tarefa 1; define o resultado do futuro
da corrotina, que será o resultado da tarefa; e depois imprime que a tarefa 1 foi finalizada.
Já a tarefa 2 é iniciada, mas aguarda o futuro
com await
. No momento em que o valor do resultado
for definido, atribuímos esse resultado à variável resultado
declarada abaixo, e finalizamos a execução da função corrotina2()
.
Agora, vamos verificar esse processo na função main()
:
# código omitido
async def main():
futuro = asyncio.Future()
tarefa1 = asyncio.create_task(corrotina1(futuro))
tarefa2 = asyncio.create_task(corrotina2(futuro))
await tarefa1
await tarefa2
asyncio.run(main())
Em main()
, definimos o futuro
; chamamos a tarefa1
, que recebe o futuro
; e também a tarefa2
, executando ambas com a função asyncio.create_task()
. No final, temos a tarefa1
iniciada, bem como a tarefa2
, mas esta última aguardando o futuro
. Após resolver o problema da tarefa1
e finalizar, passamos para a tarefa2
, finalizando com o resultado da tarefa1
.
Na programação assíncrona, precisamos saber como executar múltiplas tarefas, conforme verificado anteriormente com a função create_task()
.
Para isso, utilizamos tanto essa função quanto a gather()
, responsável por unir.
import asyncio
async def corrotina(nome, tempo):
print(f"Tarefa {nome} iniciada.")
await asyncio.sleep(tempo)
print(f"Tarefa {nome} concluída.")
async def main():
await asyncio.gather(
corrotina("1",2),
corrotina("2",3),
corrotina("3",1),
)
asyncio.run(main())
O gather()
pode substituir a função create_task()
, onde a corrotina()
de tarefa recebe o nome
e o tempo
, que varia de acordo com cada corrotina. Na função main()
, temos um await
juntando todas essas corrotinas. As corrotinas 1, 2 e 3 possuem tempos de execução diferentes.
Quando executamos a função main()
com o gather()
, podemos verificar que as três tarefas são executadas e iniciadas automaticamente juntas. No entanto, a tarefa 3 foi concluída primeiro, pois enquanto era aguardada no processo de sleep()
, a tarefa concluiu seu tempo, que era mais rápido. Assim, a tarefa 3 foi concluída em 1 segundo, a 1 em 2 segundos, e a 2 em 3 segundos.
Podemos verificar como isso aconteceu no projeto síncrono inicial, onde utilizamos import time
para trabalharmos com a biblioteca de tempo.
import time
def tarefa(numero):
print(f"Iniciando tarefa {numero}.")
time.sleep(2)
print(f"Tarefa {numero} concluída!")
tarefa(1)
tarefa(2)
tarefa(3)
Temos a função tarefa()
, que inicia a tarefa; depois pausamos o código por 2 segundos (sleep(2)
); e concluímos a tarefa, executando três tarefas de modo sequencial.
Quando adaptamos isso para um projeto assíncrono, substituímos a biblioteca time
pela biblioteca asyncio
, e definimos a função tarefa()
, antes comum, como uma função assíncrona (async
), determinando-a agora como uma corrotina. Sendo uma corrotina, podemos passá-la para uma função assíncrona também, que é a main
com await
, gather
, e executando essas três tarefas, as três corrotinas, simultaneamente. Assim, iniciamos e concluímos as tarefas 1, 2 e 3.
import asyncio
async def tarefa(numero):
print(f"Iniciando tarefa {numero}.")
await asyncio.sleep(2)
print(f"Tarefa {numero} concluída!")
async def main():
await asyncio.gather(tarefa(1), tarefa(2), tarefa(3))
asyncio.run(main())
Observação: para conhecer mais sobre a biblioteca
asyncio
, recomendamos consultar a documentação oficial do Python, onde é possível verificar módulos e pontos mais específicos para aplicar a programação assíncrona em seus projetos.
Realize as atividades do curso para praticar seus conhecimentos. Aproveite a comunidade da Alura no Discord e o fórum caso tenha dúvidas ou queira discutir o conteúdo. Além disso, compartilhe seus aprendizados nas redes sociais com #AprendizadoAlura.
Nos encontramos em uma próxima oportunidade!
O curso Praticando python: programação assíncrona possui 17 minutos de vídeos, em um total de 15 atividades. Gostou? Conheça nossos outros cursos de em Programação, ou leia nossos artigos de Programação.
Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:
Impulsione a sua carreira com os melhores cursos e faça parte da maior comunidade tech.
1 ano de Alura
Assine o PLUS e garanta:
Formações com mais de 1500 cursos atualizados e novos lançamentos semanais, em Programação, Inteligência Artificial, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.
A cada curso ou formação concluído, um novo certificado para turbinar seu currículo e LinkedIn.
No Discord, você tem acesso a eventos exclusivos, grupos de estudos e mentorias com especialistas de diferentes áreas.
Faça parte da maior comunidade Dev do país e crie conexões com mais de 120 mil pessoas no Discord.
Acesso ilimitado ao catálogo de Imersões da Alura para praticar conhecimentos em diferentes áreas.
Explore um universo de possibilidades na palma da sua mão. Baixe as aulas para assistir offline, onde e quando quiser.
Acelere o seu aprendizado com a IA da Alura e prepare-se para o mercado internacional.
1 ano de Alura
Todos os benefícios do PLUS e mais vantagens exclusivas:
Luri é nossa inteligência artificial que tira dúvidas, dá exemplos práticos, corrige exercícios e ajuda a mergulhar ainda mais durante as aulas. Você pode conversar com a Luri até 100 mensagens por semana.
Aprenda um novo idioma e expanda seus horizontes profissionais. Cursos de Inglês, Espanhol e Inglês para Devs, 100% focado em tecnologia.
Transforme a sua jornada com benefícios exclusivos e evolua ainda mais na sua carreira.
1 ano de Alura
Todos os benefícios do PRO e mais vantagens exclusivas:
Mensagens ilimitadas para estudar com a Luri, a IA da Alura, disponível 24hs para tirar suas dúvidas, dar exemplos práticos, corrigir exercícios e impulsionar seus estudos.
Envie imagens para a Luri e ela te ajuda a solucionar problemas, identificar erros, esclarecer gráficos, analisar design e muito mais.
Escolha os ebooks da Casa do Código, a editora da Alura, que apoiarão a sua jornada de aprendizado para sempre.