8.9. ThreadSafeFlag

asyncio.ThreadSafeFlag est la primitive de signalisation que asyncio fournit pour le cas que Event ne prend pas en charge : un gestionnaire d’interruption doit réveiller une tâche asyncio. L’interruption se déclenche en dehors de l’ordonnancement normal de la boucle d’événements, de sorte que la comptabilité interne qu’effectue asyncio.Event.set() ne peut pas être réalisée en toute sécurité depuis ce contexte.

8.9.1. Pourquoi une primitive distincte

asyncio.Event.set() exécute la comptabilité interne qui réveille les coroutines en attente en supposant qu”elle est appelée depuis une tâche que la boucle est en train d’exécuter. Un gestionnaire d’interruption ne satisfait pas cette hypothèse – il peut se déclencher entre deux instructions quelconques et corrompre ce que la boucle était en train de faire.

asyncio.ThreadSafeFlag.set() est écrite pour être sûre dans ces contextes. Le compromis pour cette sûreté est un ensemble de fonctionnalités plus réduit que Event : un ThreadSafeFlag ne peut être attendu que par une seule tâche à la fois, et le drapeau se réinitialise automatiquement lorsque la tâche en attente se réveille.

8.9.2. La forme de base

Une utilisation typique est une interruption GPIO qui transmet un événement d’appui sur un bouton à une tâche 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 s’exécute dans le contexte d’interruption chaque fois que le GPIO se déclenche ; tout ce qu’il fait est d’appeler flag.set(). La tâche asyncio watcher fait un await sur flag.wait() dans une boucle ; à chaque fois que le drapeau est positionné, la tâche reprend, effectue le travail requis par l’appui sur le bouton, puis revient en boucle pour attendre de nouveau.

8.9.3. Les trois méthodes

  • set() – positionne le drapeau. Si une tâche est actuellement en attente dans wait(), l’ordonnance pour qu’elle reprenne. Peut être appelée en toute sécurité depuis un gestionnaire d’interruption.

  • wait() – une coroutine. Se bloque jusqu’à ce que le drapeau soit positionné, puis renvoie. Le drapeau est automatiquement effacé au retour – le prochain appel à wait se bloquera de nouveau jusqu’au prochain set().

  • clear() – efface explicitement le drapeau sans l’attendre. Utile avant un premier wait pour écarter tout set() parasite et précoce qui se serait déclenché avant que la tâche soit prête.

8.9.4. Un seul attendeur

Une seule tâche peut se trouver dans wait() à la fois. La forme qu’une application souhaite est une unique tâche propriétaire du drapeau – généralement la même tâche que celle qui a installé le gestionnaire d’interruption – les autres tâches communiquant avec elle via Event, Lock ou des structures de données partagées protégées par ces primitives.

Lorsqu’un bouton doit réveiller plusieurs tâches, la tâche propriétaire est l’endroit où effectuer la diffusion : le watcher ci-dessus pourrait appeler some_event.set() après chaque appui, et toutes les tâches en attente sur some_event reprendraient.