8.9. ThreadSafeFlag

asyncio.ThreadSafeFlag este primitivul de semnalizare pe care asyncio îl oferă pentru cazul pe care Event nu îl suportă: un gestionar de întrerupere trebuie să trezească o sarcină asyncio. Întreruperea se declanșează în afara planificării normale a buclei de evenimente, astfel încât evidența pe care o realizează asyncio.Event.set() nu poate fi efectuată în siguranță din interiorul acesteia.

8.9.1. De ce un primitiv separat

asyncio.Event.set() execută operațiile de evidență care trezesc corutinele în așteptare pornind de la presupunerea că este apelat din interiorul unei sarcini pe care bucla o rulează în prezent. Un gestionar de întrerupere nu satisface această presupunere – se poate declanșa între oricare două instrucțiuni și poate corupe orice operație pe care bucla o avea la jumătate.

asyncio.ThreadSafeFlag.set() este scris pentru a fi sigur în acele contexte. Compromisul pentru această siguranță este un set de funcționalități mai redus decât cel al Event: un ThreadSafeFlag poate fi așteptat doar de o singură sarcină la un moment dat, iar indicatorul se resetează automat atunci când sarcina în așteptare se trezește.

8.9.2. Forma de bază

O utilizare tipică este o întrerupere GPIO care predă un eveniment de apăsare a unui buton către o sarcină 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 rulează în context de întrerupere ori de câte ori se declanșează GPIO; tot ce face este să apeleze flag.set(). Sarcina asyncio watcher execută await pe flag.wait() într-o buclă; de fiecare dată când indicatorul este setat, sarcina reia execuția, efectuează lucrul cerut de apăsarea butonului, apoi revine în buclă pentru a aștepta din nou.

8.9.3. Cele trei metode

  • set() – marchează indicatorul ca setat. Dacă o sarcină așteaptă în prezent în wait(), o planifică să reia execuția. Poate fi apelat în siguranță dintr-un gestionar de întrerupere.

  • wait() – o corutină. Blochează până când indicatorul este setat, apoi returnează. Indicatorul este șters automat la returnare – următorul apel la wait va bloca din nou până la următorul set().

  • clear() – șterge explicit indicatorul fără a aștepta pe el. Util înaintea unui prim wait pentru a elimina orice set() parazit declanșat înainte ca sarcina să fie pregătită.

8.9.4. Un singur participant la așteptare

Doar o singură sarcină poate fi în wait() la un moment dat. Forma pe care o dorește o aplicație este o singură sarcină proprietar a indicatorului – de obicei aceeași sarcină care a instalat gestionarul de întrerupere – iar celelalte sarcini comunică cu ea prin Event, Lock sau structuri de date partajate protejate de aceste primitive.

Când un buton trebuie să trezească mai multe sarcini, sarcina proprietar este locul potrivit pentru a distribui evenimentul: watcher-ul de mai sus ar putea apela some_event.set() după fiecare apăsare, iar toate sarcinile care așteaptă pe some_event ar relua execuția.