8.9. ThreadSafeFlag

asyncio.ThreadSafeFlag to prymityw sygnalizacyjny, który asyncio udostępnia dla przypadku, którego Event nie obsługuje: procedura obsługi przerwania musi obudzić zadanie asyncio. Przerwanie wyzwala się poza normalnym harmonogramowaniem pętli zdarzeń, więc ewidencja prowadzona przez asyncio.Event.set() nie może być bezpiecznie wykonana z jego wnętrza.

8.9.1. Dlaczego oddzielny prymityw

asyncio.Event.set() prowadzi ewidencję budzącą oczekujące korutyny przy założeniu, że jest wywoływana z wnętrza zadania aktualnie uruchamianego przez pętlę. Procedura obsługi przerwania nie spełnia tego założenia – może wyzwolić się pomiędzy dowolnymi dwiema instrukcjami i uszkodzić to, co pętla właśnie miała w połowie wykonane.

asyncio.ThreadSafeFlag.set() jest napisana tak, by była bezpieczna w takich kontekstach. Ceną za to bezpieczeństwo jest mniejszy zestaw funkcji niż w Event: na ThreadSafeFlag może oczekiwać tylko jedno zadanie naraz, a flaga jest automatycznie resetowana, gdy oczekujące zadanie się budzi.

8.9.2. Podstawowy kształt

Typowym zastosowaniem jest przekazanie przez przerwanie GPIO zdarzenia naciśnięcia przycisku do zadania 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 działa w kontekście przerwania za każdym razem, gdy GPIO się wyzwala; wszystko, co robi, to wywołanie flag.set(). Zadanie asyncio watcher wykonuje await na flag.wait() w pętli; za każdym razem, gdy flaga zostaje ustawiona, zadanie wznawia działanie, wykonuje pracę wymaganą przez naciśnięcie przycisku, a następnie wraca do oczekiwania.

8.9.3. Trzy metody

  • set() – oznacza flagę jako ustawioną. Jeśli jakieś zadanie aktualnie oczekuje w wait(), planuje jego wznowienie. Bezpieczna do wywołania z procedury obsługi przerwania.

  • wait() – korutyna. Blokuje, dopóki flaga nie zostanie ustawiona, a następnie zwraca sterowanie. Flaga jest automatycznie czyszczona przy powrocie – kolejne wywołanie wait ponownie zablokuje aż do następnego set().

  • clear() – jawnie czyści flagę bez oczekiwania na nią. Przydatne przed pierwszym wait, aby odrzucić wszelkie błędne, przedwczesne set(), które wyzwoliły się, zanim zadanie było gotowe.

8.9.4. Tylko jeden oczekujący

W wait() może znajdować się tylko jedno zadanie naraz. Kształt, jakiego oczekuje aplikacja, to pojedyncze zadanie właściciel flagi – zwykle to samo zadanie, które zainstalowało procedurę obsługi przerwania – z innymi zadaniami komunikującymi się z nim za pośrednictwem Event, Lock lub współdzielonych struktur danych chronionych przez te prymitywy.

Gdy przycisk musi obudzić kilka zadań, miejscem na rozdzielenie jest zadanie właściciela: powyższy watcher mógłby po każdym naciśnięciu wywołać some_event.set(), a wszystkie zadania oczekujące na some_event wznowiłyby działanie.