8.9. ThreadSafeFlag

asyncio.ThreadSafeFlag is de signaleringsprimitief die asyncio biedt voor het geval dat Event niet ondersteunt: een interrupt-handler moet een asyncio-taak wekken. De interrupt vuurt buiten de normale planning van de event-loop, dus de administratie die asyncio.Event.set() doet kan er niet veilig vanuit gedaan worden.

8.9.1. Waarom een aparte primitief

asyncio.Event.set() voert de administratie uit die wachtende coroutines wekt onder de aanname dat hij wordt aangeroepen vanuit een taak die de loop momenteel uitvoert. Een interrupt-handler voldoet niet aan die aanname – hij kan tussen elke twee instructies vuren en beschadigen wat de loop halverwege aan het doen was.

asyncio.ThreadSafeFlag.set() is zo geschreven dat hij veilig is in die contexten. De afweging voor die veiligheid is een kleinere set functies dan Event: op een ThreadSafeFlag kan slechts één taak tegelijk wachten, en de flag wordt automatisch gereset wanneer de wachtende taak ontwaakt.

8.9.2. De basisvorm

Een typisch gebruik is een GPIO-interrupt die een knopdruk-gebeurtenis doorgeeft aan een asyncio-taak:

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 draait in interrupt-context telkens wanneer de GPIO vuurt; het enige wat het doet is flag.set() aanroepen. De asyncio-taak watcher awaitt flag.wait() in een loop; telkens wanneer de flag wordt gezet, hervat de taak, voert het werk uit dat de knopdruk vereist en gaat dan terug naar wachten.

8.9.3. De drie methoden

  • set() – markeert de flag als gezet. Als een taak momenteel wacht in wait(), wordt hij ingepland om te hervatten. Veilig om aan te roepen vanuit een interrupt-handler.

  • wait() – een coroutine. Blokkeert tot de flag gezet is en keert dan terug. De flag wordt bij terugkeer automatisch gewist – de volgende aanroep van wait blokkeert opnieuw tot de volgende set().

  • clear() – wist de flag expliciet zonder erop te wachten. Handig vóór een eerste wait om onechte vroege set() te verwerpen die vuurde voordat de taak klaar was.

8.9.4. Slechts één wachtende

Slechts één taak mag tegelijk in wait() zijn. De vorm die een toepassing wil is één enkele eigenaar-taak voor de flag – meestal dezelfde taak die de interrupt-handler installeerde – waarbij andere taken ermee communiceren via Event, Lock of gedeelde datastructuren die door die primitieven worden beschermd.

Wanneer een knop meerdere taken moet wekken, is de eigenaar-taak de plek om uit te waaieren: de bovenstaande watcher zou some_event.set() kunnen aanroepen na elke druk, en alle taken die op some_event wachten zouden hervatten.