8.15. Armadilhas#
Os mesmos padrões que tornam o asyncio agradável – sem preempção, awaits explícitos – lhe dão seu próprio conjunto de formatos que mordem. Esta página é o catálogo daqueles que aparecem com frequência suficiente para valer a pena conhecer.
8.15.1. Esquecer do await#
Chamar uma função async def retorna um objeto corrotina. Isso não executa o corpo da função. Para de fato executá-lo, a corrotina precisa ser awaited ou encapsulada em uma tarefa:
async def main():
send_request() # bug: returns the coroutine, does nothing
await send_request() # right: run it to completion
asyncio.create_task(send_request()) # right: run it concurrently
O bug é silencioso – o objeto corrotina é criado, descartado e nunca executado. A aplicação prossegue como se tudo tivesse funcionado. O MicroPython às vezes registra um aviso de que uma corrotina nunca foi aguardada; às vezes não. Audite por awaits ausentes em cada ponto de chamada que se pareça com uma chamada de função.
8.15.2. Laços apertados sem await#
Uma corrotina que executa em um laço e nunca faz await monopoliza o event loop. Nenhuma outra tarefa avança até que o laço termine ou ceda o controle:
async def counter():
n = 0
while True:
n += 1 # bug: starves the loop
A correção é um yield dentro do laço – geralmente await asyncio.sleep_ms(0) – para que outras tarefas prontas tenham a chance de executar. Trabalho pesado de computação também pertence a esse formato: um laço de processamento de imagem que executa por centenas de milissegundos por iteração deve ceder o controle ao menos uma vez por iteração para que o restante do programa não trave.
8.15.3. Engolir o CancelledError#
A página sobre cancelamento já cobriu isso em detalhes. Repetimos aqui porque é a causa mais comum de “minha aplicação não desliga”: uma corrotina captura asyncio.CancelledError para fins de limpeza e esquece de relançá-lo. A tarefa continua a executar; o chamador que pediu o cancelamento fica pendurado para sempre esperando que ela termine. Sempre relance após a limpeza, ou use um bloco try/finally em vez de um except explícito.
8.15.5. await em nível de módulo#
await só é válido dentro de um corpo async def. Escrevê-lo em nível de módulo – fora de qualquer corrotina – é um erro de sintaxe:
# bug: not inside an async def
result = await fetch()
A correção é colocar o trabalho em uma corrotina e chamá-la a partir do ponto de entrada asyncio.run() do programa.
8.15.6. Múltiplas chamadas a asyncio.run#
O MicroPython tem um event loop. Chamar asyncio.run() duas vezes seguidas – uma para configuração, outra para o trabalho principal – ainda usa o mesmo loop. Chamá-lo de dentro de uma corrotina em execução é um erro: o loop já está em execução. Ambos os casos surgem com mais frequência quando um script cresce organicamente e o autor tenta estendê-lo adicionando mais chamadas a run() em vez de incorporar o novo trabalho ao main existente.
8.15.7. Uso de Event a partir de uma interrupção#
asyncio.Event.set() só pode ser chamado com segurança de dentro do event loop. Chamá-lo a partir de um manipulador de interrupção de GPIO é um risco de corrupção. Para acordar uma tarefa a partir de uma interrupção, use ThreadSafeFlag em vez disso – a página sobre ele cobre o formato.
8.15.8. Chamadas síncronas longas#
Uma corrotina pode aguardar as próprias primitivas de espera do asyncio; qualquer outra coisa que ela chama executa de forma síncrona e bloqueia o loop até retornar. Um time.sleep() bloqueante de 200 ms, uma escrita no cartão SD que leva 80 ms para ser descarregada, uma grande compressão JPEG, uma chamada a csi.CSI.snapshot() – cada uma delas retém o event loop por toda a sua duração. A correção depende da chamada:
Para
time.sleep: substitua-o porawait asyncio.sleepouawait asyncio.sleep_ms.Para
csi.CSI.snapshot: use o invólucro de snapshot assíncrono que a página de captura constrói.Para computação longa (processamento de imagem, codificação JPEG): aceite o custo ou divida o trabalho em blocos que fazem
awaitentre as iterações.
O asyncio não pode tornar uma chamada síncrona não bloqueante. Ele só pode permitir que outras corrotinas executem enquanto algo mais está sendo aguardado.