8.15. Armadilhas¶
Os mesmos padrões que tornam o asyncio agradável – sem preempção, awaits explícitos – conferem-lhe o seu próprio conjunto de armadilhas. Esta página é o catálogo das que surgem com frequência suficiente para valer a pena conhecer.
8.15.1. Esquecer o await¶
Chamar uma função async def devolve um objeto coroutine. O corpo da função não é executado. Para o executar de facto, a coroutine tem de ser chamada com awaitou encapsulada numa 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 erro é silencioso – o objeto coroutine é criado, descartado e nunca executado. A aplicação prossegue como se tudo tivesse funcionado. O MicroPython por vezes regista um aviso indicando que uma coroutine nunca foi aguardada; outras vezes não o faz. Audite os awaits em falta em todos os pontos de chamada que pareçam chamadas a funções.
8.15.2. Ciclos apertados sem await¶
Uma coroutine que corre em ciclo e nunca faz awaits monopoliza o ciclo de eventos. Nenhuma outra tarefa avança até o ciclo terminar ou ceder:
async def counter():
n = 0
while True:
n += 1 # bug: starves the loop
A correção consiste numa cedência dentro do ciclo – geralmente await asyncio.sleep_ms(0) – para que outras tarefas prontas tenham oportunidade de correr. O trabalho computacionalmente intensivo também deve ter esta forma: um ciclo de processamento de imagem que demora centenas de milissegundos por iteração deve ceder pelo menos uma vez por iteração para que o resto do programa não fique bloqueado.
8.15.3. Engolir CancelledError¶
A página de cancelamento já abordou isto em detalhe. Repete-se aqui porque é a causa mais comum de «a minha aplicação não encerra»: uma coroutine captura asyncio.CancelledError para efeitos de limpeza e esquece-se de a relançar. A tarefa continua a correr; o chamador que pediu o cancelamento fica à espera para sempre que ela termine. Relance sempre após a limpeza, ou use um bloco try/finally em vez de um except explícito.
8.15.5. await ao nível do módulo¶
await só é válido dentro de um corpo async def. Escrevê-lo ao nível do módulo – fora de qualquer coroutine – é um erro de sintaxe:
# bug: not inside an async def
result = await fetch()
A correção consiste em colocar o trabalho numa coroutine e chamá-la a partir do ponto de entrada asyncio.run() do programa.
8.15.6. Múltiplas chamadas asyncio.run¶
O MicroPython tem um ciclo de eventos. Chamar asyncio.run() duas vezes seguidas – uma para a configuração, outra para o trabalho principal – continua a usar o mesmo ciclo. Chamá-lo de dentro de uma coroutine em execução é um erro: o ciclo já está a correr. Ambos os casos surgem mais frequentemente quando um script cresce organicamente e o autor tenta expandi-lo adicionando mais chamadas run() em vez de incorporar o novo trabalho no main existente.
8.15.7. Utilização de Event a partir de uma interrupção¶
asyncio.Event.set() só é seguro de chamar de dentro do ciclo de eventos. Chamá-lo a partir de um handler de interrupção GPIO é um risco de corrupção. Para acordar uma tarefa a partir de uma interrupção, use ThreadSafeFlag – a página sobre este tema aborda a forma correta.
8.15.8. Chamadas síncronas longas¶
Uma coroutine pode aguardar as primitivas de espera do asyncio; qualquer outra coisa que chame corre de forma síncrona e bloqueia o ciclo até retornar. Um time.sleep() bloqueante de 200 ms, uma escrita em cartão SD que demora 80 ms a consolidar, uma compressão JPEG grande, uma chamada csi.CSI.snapshot() – cada uma destas retém o ciclo de eventos pela sua duração total. A correção depende da chamada:
Para
time.sleep: substitua porawait asyncio.sleepouawait asyncio.sleep_ms.Para
csi.CSI.snapshot: use o wrapper assíncrono de captura 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 partes que fazem
awaitentre iterações.
O asyncio não pode tornar uma chamada síncrona não-bloqueante. Só pode permitir que outras coroutines corram enquanto outra coisa está em await.