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.