8.6. חריגות

חריגות בתוך סקריפט asyncio מתנהגות כמעט באותו אופן כמו ב-Python רגיל – הן מתפשטות במעלה שרשרת הקריאות עד שמשהו תופס אותן. כמעט מפני שמשימות רצות במקביל, ולכן הנתיב ”כלפי מעלה“ אינו הנתיב שיצר את המשימה. עמוד זה מכסה לאן הולכות החריגות בכל אחת מהצורות הנפוצות.

8.6.1. בתוך קורוטינה אחת

try/except בתוך קורוטינה תופס חריגות שמועלות על ידי כל דבר שהוא awaitעליו, באופן הרגיל:

async def fetch_with_retry(url):
    for attempt in range(3):
        try:
            return await fetch(url)
        except OSError as e:
            last_error = e
    raise last_error

אין כאן שום דבר ייחודי ל-asyncio – סעיף ה-except רואה חריגות שמועלות בתוך fetch כאילו הייתה זו קריאת פונקציה רגילה.

8.6.2. במשימה שהיישום ממתין לה

כשקורוטינה הרצה כ-Task מעלה חריגה, החריגה נשמרת על המשימה. בפעם הבאה שמשהו awaitלמשימה הזו, החריגה מועלית מחדש ב-await

task = asyncio.create_task(may_fail())
try:
    result = await task
except OSError:
    log("may_fail failed")

אותו דבר חל על asyncio.gather(). ההתנהגות שכברירת מחדל – ילד אחד מעלה חריגה, האחרים מבוטלים, החריגה מתפשטת החוצה מה-gather – נובעת מהמנגנון הזה.

8.6.3. במשימה שאיש אינו ממתין לה

משימה שאיש לעולם אינו awaitלה היא המקרה הזקוק לתשומת לב. החריגה עדיין מתרחשת; הלולאה מבחינה שהמשימה הסתיימה עם חריגה לא מטופלת; אך אין await שדרכו היא תצוף. ההתנהגות שכברירת מחדל היא להדפיס traceback דרך sys.stderr ולהמשיך לרוץ – מה שמתאים לאבחון לא מבוקר, אך אינו מתאים ליישום שרצה לדעת.

התיקון הנכון הוא בדרך כלל להמתין למשימה. בין אם ישירות, על ידי זכירת הידית והמתנה לה במהלך הכיבוי, או באופן עקיף דרך gather() או wait_for(). דפוס ”כיבוי יישום“ של עמוד פסקי זמן וביטול תופס מקרה זה עבור משימות הרקע ארוכות-החיים שסקריפט טיפוסי מייצר.

8.6.4. מטפל חריגות מותאם אישית

כש-traceback-והמשך שקט אינו מספיק, הלולאה חושפת וו – Loop.set_exception_handler – שהיישום יכול לדרוס כדי לעשות משהו אחר:

def handler(loop, context):
    print("asyncio:", context.get("message"))
    if "exception" in context:
        sys.print_exception(context["exception"])

loop = asyncio.get_event_loop()
loop.set_exception_handler(handler)

הארגומנט context הוא מילון עם המפתחות 'message', 'exception' ו-'future'. החריגה עשויה להיעדר באירועים מסוימים בסגנון אזהרה, ולכן הדוגמה משתמשת ב-.get().

שימושים טיפוסיים הם לתעד את הכשל ל-flash, להבהב LED שגיאה, או להסלים לאתחול watchdog. עמוד בקרת לולאה מכסה את כל המשטח של ווי הלולאה.

8.6.5. KeyboardInterrupt

כשסקריפט נעצר מבחוץ – בדרך כלל על ידי בקשת ה-IDE לעצור אותו – הבקשה מגיעה אל תוך הסקריפט כ-KeyboardInterrupt. בתוך asyncio.run() היא מתפשטת כפי שכל חריגה לא מטופלת אחרת הייתה מתפשטת: main מבוטל, כל משימה שהלולאה עוקבת אחריה מבוטלת גם היא, וה-KeyboardInterrupt מועלית מחדש החוצה מ-asyncio.run(). סעיפי finally רצים בדרך החוצה, כך שאותו דפוס ניקוי מעמוד הביטול הוא מה שמטפל בכך.