8.9. ThreadSafeFlag

asyncio.ThreadSafeFlag הוא פרימיטיב האיתות ש-asyncio מספק עבור המקרה ש-Event אינו תומך בו: מטפל פסיקה צריך להעיר משימת asyncio. הפסיקה נורית מחוץ לתזמון הרגיל של לולאת האירועים, ולכן רישומי החשבונאות ש-asyncio.Event.set() מבצעת אינם יכולים להתבצע בבטחה מתוכה.

8.9.1. מדוע פרימיטיב נפרד

asyncio.Event.set() מריצה את רישומי החשבונאות שמעירים קורוטינות ממתינות מתוך הנחה שהיא נקראת מתוך משימה שהלולאה מריצה כעת. מטפל פסיקה אינו עומד בהנחה זו – הוא יכול להירות בין כל שתי הוראות ולהשחית את כל מה שהלולאה הייתה באמצע ביצועו.

asyncio.ThreadSafeFlag.set() נכתבה כדי להיות בטוחה בהקשרים אלה. התמורה לבטיחות הזו היא מערך תכונות קטן יותר מאשר Event: על ThreadSafeFlag יכולה להמתין רק משימה אחת בכל רגע נתון, והדגל מתאפס אוטומטית כאשר המשימה הממתינה מתעוררת.

8.9.2. הצורה הבסיסית

שימוש טיפוסי הוא פסיקת GPIO שמעבירה אירוע לחיצת לחצן למשימת 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 רצה בהקשר פסיקה בכל פעם שה-GPIO נורה; כל מה שהיא עושה הוא לקרוא ל-flag.set(). משימת ה-asyncio watcher מבצעת await ל-flag.wait() בלולאה; בכל פעם שהדגל נקבע, המשימה מתחדשת, מריצה את כל העבודה שלחיצת הלחצן דורשת, ואז חוזרת בלולאה להמתין שוב.

8.9.3. שלוש השיטות

  • set() – מסמנת את הדגל כקבוע. אם משימה ממתינה כעת ב-wait(), מתזמנת אותה להתחדש. בטוח לקרוא לה ממטפל פסיקה.

  • wait() – קורוטינה. חוסמת עד שהדגל נקבע, ואז חוזרת. הדגל מנוקה אוטומטית בעת החזרה – הקריאה הבאה ל-wait תחסום שוב עד ה-set() הבא.

  • clear() – מנקה במפורש את הדגל מבלי להמתין עליו. שימושי לפני wait ראשון כדי להשליך כל set() מוקדם ושגוי שנורה לפני שהמשימה הייתה מוכנה.

8.9.4. ממתין אחד בלבד

רק משימה אחת יכולה להיות ב-wait() בכל רגע נתון. הצורה שיישום רוצה היא משימת בעלים יחידה עבור הדגל – בדרך כלל אותה משימה שהתקינה את מטפל הפסיקה – כאשר משימות אחרות מתקשרות איתה דרך Event, Lock, או מבני נתונים משותפים המוגנים על ידי הפרימיטיבים הללו.

כאשר לחצן צריך להעיר מספר משימות, משימת הבעלים היא המקום לפזר ממנו: ה-watcher לעיל יכול לקרוא ל-some_event.set() אחרי כל לחיצה, וכל המשימות הממתינות על some_event יתחדשו.