8.9. ThreadSafeFlag

asyncio.ThreadSafeFlag es la primitiva de señalización que asyncio proporciona para el caso que Event no admite: un manejador de interrupciones necesita despertar a una tarea asyncio. La interrupción se dispara fuera de la planificación normal del bucle de eventos, por lo que el trabajo de contabilidad que hace asyncio.Event.set() no puede realizarse de forma segura desde su interior.

8.9.1. Por qué una primitiva aparte

asyncio.Event.set() ejecuta el trabajo de contabilidad que despierta a las corrutinas en espera bajo la suposición de que se está llamando desde dentro de una tarea que el bucle está ejecutando en ese momento. Un manejador de interrupciones no cumple esa suposición – puede dispararse entre dos instrucciones cualesquiera y corromper lo que el bucle estuviera haciendo a medias.

asyncio.ThreadSafeFlag.set() está escrito para ser seguro en esos contextos. El precio de esa seguridad es un conjunto de funcionalidades menor que el de Event: una ThreadSafeFlag solo puede ser esperada por una tarea a la vez, y el indicador se restablece automáticamente cuando la tarea en espera se despierta.

8.9.2. La estructura básica

Un uso típico es una interrupción de GPIO que entrega un evento de pulsación de botón a una tarea 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 se ejecuta en contexto de interrupción cada vez que se dispara el GPIO; lo único que hace es llamar a flag.set(). La tarea asyncio watcher hace await de flag.wait() en un bucle; cada vez que se establece el indicador, la tarea se reanuda, ejecuta el trabajo que requiera la pulsación del botón y luego vuelve al bucle para esperar de nuevo.

8.9.3. Los tres métodos

  • set() – marca el indicador como establecido. Si una tarea está esperando actualmente en wait(), la planifica para que se reanude. Es seguro llamarlo desde un manejador de interrupciones.

  • wait() – una corrutina. Se bloquea hasta que el indicador esté establecido y entonces retorna. El indicador se borra automáticamente al retornar – la siguiente llamada a wait volverá a bloquearse hasta el próximo set().

  • clear() – borra explícitamente el indicador sin esperar en él. Resulta útil antes de un primer wait para descartar cualquier set() espurio y prematuro que se haya disparado antes de que la tarea estuviera lista.

8.9.4. Un solo esperador

Solo una tarea puede estar en wait() a la vez. La estructura que conviene a una aplicación es una única tarea propietaria del indicador – normalmente la misma tarea que instaló el manejador de interrupciones – con otras tareas comunicándose con ella mediante Event, Lock o estructuras de datos compartidas protegidas por esas primitivas.

Cuando un botón tiene que despertar a varias tareas, la tarea propietaria es el lugar para distribuir: el watcher anterior podría llamar a some_event.set() después de cada pulsación, y todas las tareas que esperan en some_event se reanudarían.