8.9. ThreadSafeFlag

asyncio.ThreadSafeFlag é a primitiva de sinalização que o asyncio fornece para o caso que Event não suporta: um handler de interrupção precisa de acordar uma tarefa asyncio. A interrupção dispara fora do agendamento normal do ciclo de eventos, pelo que a manutenção de registos que asyncio.Event.set() faz não pode ser feita de forma segura a partir dele.

8.9.1. Porquê uma primitiva separada

asyncio.Event.set() executa a manutenção de registos que acorda as coroutines em espera partindo do pressuposto de que está a ser chamado de dentro de uma tarefa que o ciclo está atualmente a correr. Um handler de interrupção não satisfaz esse pressuposto – pode disparar entre quaisquer duas instruções e corromper o que quer que o ciclo estivesse a fazer a meio.

asyncio.ThreadSafeFlag.set() está escrito para ser seguro nesses contextos. A compensação por essa segurança é um conjunto de funcionalidades mais reduzido do que Event: um ThreadSafeFlag só pode ser aguardado por uma tarefa de cada vez, e a flag é automaticamente reposta quando a tarefa em espera acorda.

8.9.2. A forma básica

Uma utilização típica é um handler de interrupção GPIO a transferir um evento de pressão de botão para uma tarefa asyncio:

import asyncio
from machine import Pin

flag = asyncio.ThreadSafeFlag()

def on_press(pin):
    flag.set()

async def watcher():
    button = Pin("P1", Pin.IN, Pin.PULL_UP)
    button.irq(handler=on_press, trigger=Pin.IRQ_FALLING)
    while True:
        await flag.wait()
        print("button pressed")

asyncio.run(watcher())

on_press corre em contexto de interrupção sempre que o GPIO dispara; tudo o que faz é chamar flag.set(). A tarefa asyncio watcher faz awaitem flag.wait() num ciclo; cada vez que a flag é definida, a tarefa retoma, executa o trabalho que a pressão do botão requer, e volta ao ciclo de espera.

8.9.3. Os três métodos

  • set() – marca a flag como definida. Se uma tarefa estiver atualmente em espera em wait(), agenda-a para retomar. Seguro para chamar a partir de um handler de interrupção.

  • wait() – uma coroutine. Bloqueia até a flag estar definida, depois retorna. A flag é automaticamente limpa no retorno – a próxima chamada a wait bloqueará novamente até ao próximo set().

  • clear() – limpa explicitamente a flag sem esperar por ela. Útil antes de um primeiro wait para descartar quaisquer set() espúrios que tenham disparado antes de a tarefa estar pronta.

8.9.4. Apenas um aguardador

Apenas uma tarefa pode estar em wait() de cada vez. A forma que uma aplicação quer é uma única tarefa proprietária da flag – geralmente a mesma tarefa que instalou o handler de interrupção – com outras tarefas a comunicar com ela através de Event, Lock, ou estruturas de dados partilhadas protegidas por essas primitivas.

Quando um botão tem de acordar várias tarefas, a tarefa proprietária é o local para fazer o encaminhamento: o observador acima poderia chamar some_event.set() após cada pressão, e todas as tarefas em espera em some_event retomavam.