8.9. ThreadSafeFlag#

asyncio.ThreadSafeFlag je signalizační primitiv, který asyncio poskytuje pro případ, jejž Event nepodporuje: obsluha přerušení potřebuje probudit asyncio úlohu. Přerušení nastane mimo běžné plánování smyčky událostí, takže evidenci, kterou provádí asyncio.Event.set(), nelze z jejího nitra bezpečně vykonat.

8.9.1. Proč samostatný primitiv#

asyncio.Event.set() provádí evidenci, která probouzí čekající korutiny, za předpokladu, že je volána zevnitř úlohy, kterou smyčka právě běží. Obsluha přerušení tento předpoklad nesplňuje – může nastat mezi libovolnými dvěma instrukcemi a poškodit cokoli, co měla smyčka rozdělané.

asyncio.ThreadSafeFlag.set() je napsána tak, aby byla v takových kontextech bezpečná. Kompromisem za tuto bezpečnost je menší sada funkcí oproti Event: na ThreadSafeFlag může čekat vždy jen jedna úloha v daném okamžiku a příznak se po probuzení čekající úlohy automaticky resetuje.

8.9.2. Základní tvar#

Typickým použitím je přerušení GPIO, které předává událost stisknutí tlačítka asyncio úloze:

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 běží v kontextu přerušení pokaždé, když se GPIO spustí; vše, co dělá, je volání flag.set(). Asyncio úloha watcher ve smyčce volá await na flag.wait(); pokaždé, když je příznak nastaven, úloha pokračuje, vykoná veškerou práci, kterou stisknutí tlačítka vyžaduje, a poté se vrátí zpět do čekání.

8.9.3. Tři metody#

  • set() – označí příznak jako nastavený. Pokud úloha právě čeká v wait(), naplánuje její pokračování. Bezpečné volat z obsluhy přerušení.

  • wait() – korutina. Blokuje, dokud není příznak nastaven, a poté se vrátí. Příznak se při návratu automaticky vymaže – další volání wait bude opět blokovat až do dalšího set().

  • clear() – explicitně vymaže příznak bez čekání na něj. Užitečné před prvním wait k zahození jakéhokoli falešného předčasného set(), který se spustil dříve, než byla úloha připravena.

8.9.4. Pouze jeden čekající#

V wait() může být v daném okamžiku pouze jedna úloha. Tvar, který aplikace chce, je jediná vlastnící úloha příznaku – obvykle tatáž úloha, která nainstalovala obsluhu přerušení – přičemž ostatní úlohy s ní komunikují prostřednictvím Event, Lock nebo sdílených datových struktur chráněných těmito primitivy.

Když tlačítko musí probudit několik úloh, je vlastnící úloha místem, odkud se rozvětvit: výše uvedený watcher by mohl po každém stisknutí zavolat some_event.set() a všechny úlohy čekající na some_event by pokračovaly.