8.9. ThreadSafeFlag

asyncio.ThreadSafeFlag e la primitiva di segnalazione che asyncio fornisce per il caso che Event non supporta: un gestore di interrupt deve risvegliare un task asyncio. L’interrupt scatta al di fuori del normale scheduling dell’event loop, quindi la contabilita che asyncio.Event.set() esegue non puo essere fatta in sicurezza dal suo interno.

8.9.1. Perche una primitiva separata

asyncio.Event.set() esegue la contabilita che risveglia le coroutine in attesa partendo dal presupposto che venga chiamata dall’interno di un task che il loop sta attualmente eseguendo. Un gestore di interrupt non soddisfa quel presupposto: puo scattare tra due istruzioni qualsiasi e corrompere qualunque cosa il loop stesse facendo a meta.

asyncio.ThreadSafeFlag.set() e scritta per essere sicura in quei contesti. Il compromesso per quella sicurezza e un insieme di funzionalita piu ridotto rispetto a Event: una ThreadSafeFlag puo essere attesa da un solo task alla volta, e il flag si auto-reimposta quando il task in attesa si risveglia.

8.9.2. La struttura di base

Un uso tipico e un interrupt GPIO che passa un evento di pressione di un pulsante a un task 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 viene eseguita in contesto di interrupt ogni volta che il GPIO scatta; tutto cio che fa e chiamare flag.set(). Il task asyncio watcher esegue await su flag.wait() in un loop; ogni volta che il flag viene impostato, il task riprende, esegue qualunque lavoro richieda la pressione del pulsante, poi torna ad attendere di nuovo.

8.9.3. I tre metodi

  • set() – marca il flag come impostato. Se un task e attualmente in attesa in wait(), lo schedula per la ripresa. Puo essere chiamato in sicurezza da un gestore di interrupt.

  • wait() – una coroutine. Si blocca finche il flag non viene impostato, poi ritorna. Il flag viene azzerato automaticamente al ritorno: la chiamata successiva a wait si blocchera di nuovo fino alla prossima set().

  • clear() – azzera esplicitamente il flag senza attenderlo. Utile prima di una prima wait per scartare eventuali set() spurie e premature scattate prima che il task fosse pronto.

8.9.4. Un solo attendente

Solo un task puo trovarsi in wait() alla volta. La struttura che un’applicazione vuole e un singolo task proprietario del flag – di solito lo stesso task che ha installato il gestore di interrupt – con gli altri task che comunicano con esso tramite Event, Lock o strutture dati condivise protette da quelle primitive.

Quando un pulsante deve risvegliare diversi task, il task proprietario e il punto in cui diramare: il watcher di cui sopra potrebbe chiamare some_event.set() dopo ogni pressione, e tutti i task in attesa su some_event riprenderebbero.