8.2. Corrotinas e tarefas¶
As corrotinas são a unidade de trabalho a partir da qual se constrói um programa asyncio; as tarefas são a forma como uma aplicação executa várias corrotinas em simultâneo.
8.2.1. Corrotinas¶
Uma corrotina é uma função declarada com async def
import asyncio
async def heartbeat(interval_ms):
while True:
print("tick")
await asyncio.sleep_ms(interval_ms)
O corpo parece uma função normal, com um ingrediente extra: await. Onde quer que a corrotina tenha de aguardar por algo – um sleep, uma leitura de rede, um evento a ser definido – ela faz awaitde uma expressão que sabe como suspender a corrotina até que aquilo por que está à espera esteja pronto. Em cada await, a corrotina devolve o controlo ao asyncio; o asyncio retoma-a a partir do mesmo ponto assim que a operação aguardada tiver concluído.
O módulo asyncio inclui dois sleeps:
asyncio.sleep()– argumento em segundos, aceita um float.asyncio.sleep_ms()– argumento em milissegundos, recebe um int. Uma extensão MicroPython; normalmente a escolha certa na câmara porque os controlos de temporização no firmware são em milissegundos.
Um simples async def por si só não faz nada. Chamar heartbeat(500) não executa o corpo; devolve um objeto corrotina que o asyncio tem de agendar. A forma mais simples de agendar uma é asyncio.run()
asyncio.run(heartbeat(500))
asyncio.run() inicia o ciclo de eventos, agenda a corrotina que lhe foi passada como ponto de entrada de nível superior, conduz o ciclo até essa corrotina terminar e, em seguida, encerra o ciclo. Para uma única corrotina, isso é o programa inteiro. Para várias corrotinas, a aplicação recorre a tarefas.
8.2.2. Tarefas¶
Uma tarefa é o invólucro asyncio em torno de uma corrotina que diz agendar isto em simultâneo com a atual e deixa-me continuar. asyncio.create_task() cria uma e devolve um objeto Task que representa o trabalho agendado:
task = asyncio.create_task(heartbeat("fast", 100))
A corrotina está agora no agendamento do ciclo; quem a chamou não esperou por ela. O Task devolvido é o identificador que o chamador usa depois para interagir com esse trabalho em execução.
Assim que a aplicação tiver o identificador, pode fazer três coisas com ele:
Aguardar que a tarefa termine. Um
Taské ele próprio aguardável.result = await tasksuspende a corrotina atual até que a corrotina detaskretorne, e depois retoma com o que essa corrotina devolveu (ou volta a lançar o que ela lançou).Cancelar a tarefa.
task.cancel()agenda o lançamento deasyncio.CancelledErrordentro da corrotina da tarefa no seu próximoawait, dando-lhe a oportunidade de executar código de limpeza num blocofinally. A página sobre timeouts e cancelamento cobre os detalhes.Identificá-la mais tarde.
asyncio.current_task()devolve oTaskda corrotina que está atualmente em execução. A maioria dos scripts nunca o chama; aparece em instrumentação e em gestores de exceções.
O script não tem de capturar o identificador sempre. As tarefas de segundo plano descartáveis que a aplicação inicia e deixa correr podem descartar o valor de retorno – o ciclo continua a agendá-las:
import asyncio
async def heartbeat(name, interval_ms):
while True:
print(name)
await asyncio.sleep_ms(interval_ms)
async def main():
asyncio.create_task(heartbeat("fast", 100))
asyncio.create_task(heartbeat("slow", 500))
await asyncio.sleep(5)
asyncio.run(main())
As duas chamadas create_task agendam ambos os heartbeats sem esperar por nenhum deles. O controlo regressa imediatamente a main, que depois faz awaitde um sleep de cinco segundos. Enquanto dorme, as duas tarefas de heartbeat progridem; o ciclo percorre as tarefas que estão prontas para executar. Após cinco segundos, main retorna, o ciclo encerra quaisquer tarefas ainda ativas, e asyncio.run() devolve o controlo ao chamador.
Capture o identificador sempre que a aplicação efetivamente precisar de uma das três operações acima. Na prática isso significa quase sempre, porque encerrar corretamente uma aplicação implica cancelar as tarefas de segundo plano que ela criou – a página de cancelamento cobre o padrão.
8.2.3. A regra das duas linhas¶
O programa asyncio mínimo são as duas linhas com que os exemplos acima terminam:
async def main():
...
asyncio.run(main())
Tudo o resto – as tarefas que a aplicação cria, as primitivas com que as coordena, os streams que abre – acontece dentro de main (e dentro das corrotinas que main cria). Quando um script supera o ciclo clássico while True: csi0.snapshot() da câmara, a resposta não é chamar asyncio.run() em vários lugares; é incorporar o novo trabalho em main como mais tarefas.