2.32. קורוטינות

קורוטינה היא פונקציה שיכולה להיעצר באמצע ולהמשיך מאוחר יותר מהנקודה שבה עצרה, כשכל המשתנים המקומיים שלה נשמרים. היא משקפת את דפוס הגנרטור – גוף פונקציה שניתן להשהות ולחדש – עם שינוי אחד: מי מניע כל חידוש.

התחביר של Python לכתיבת קורוטינה הוא צמד מילות המפתח async / await. async מסמן את הפונקציה כקורוטינה; await מסמן את הנקודות בתוכה שבהן מותר להשהות.

2.32.1. הגדרת קורוטינה

פונקציה שהוגדרה עם async def היא פונקציית קורוטינה. קריאה לה אינה מריצה את הגוף; היא מחזירה אובייקט קורוטינה שעדיין לא התחיל:

async def greet(name):
    print("hello,", name)

coro = greet("Alice")        # body NOT run yet

אובייקט הקורוטינה מושהה ממש בתחילת הפונקציה. משהו חייב להניע אותו כדי שהגוף ירוץ – אותו משהו הוא לולאת אירועים (event loop), רכיב זמן ריצה. ל-MicroPython יש כזו במודול asyncio. לעת עתה, התייחס לקורוטינה כ“מוכנה לריצה, ממתינה למניע“.

2.32.2. השהיה בתוך קורוטינה

בתוך קורוטינה, ביטוי await משהה את הביצוע עד שהערך שמחכים לו מוכן:

async def fetch_and_log():
    data = await read_sensor()
    print("got:", data)

כאשר הגוף מגיע ל-await read_sensor(), הקורוטינה מחזירה את השליטה לכל מה שמריץ אותה. כאשר read_sensor() מסתיימת, המניע מחדש את הקורוטינה בשורה הבאה, כשהתוצאה מקושרת ל-data.

await תקף רק בתוך קורוטינה. שימוש בו בפונקציה רגילה הוא שגיאת תחביר.

2.32.3. הקשר לגנרטורים

קורוטינות וגנרטורים חולקים את אותו מנגנון בסיסי. ההבדל הוא מי מושך כל חידוש:

  • גנרטור מניב ערכים; הצרכן מושך את הערך הבא עם next() או על ידי איטרציה.

  • קורוטינה מניבה שליטה; לולאת אירועים מתזמנת את החידוש כאשר הפעולה שמחכים לה מוכנה.

אם לחיצת היד של הנבת הגנרטור (yield) הגיונית, לחיצת היד של הקורוטינה היא אותו רעיון – רק שמונעת על ידי לולאת אירועים במקום לולאת for.

לולאת אירועים היא מתזמן קטן ששומר רשימה של קורוטינות הממתינות למשהו (טיימר, אירוע רשת, סיום של קורוטינה אחרת). בכל איטרציה היא בוחרת קורוטינה שהמתנתה הסתיימה, מחדשת אותה עד ה-await הבא, ואז רושמת למה הקורוטינה ממתינה כעת וממשיכה לקורוטינה מוכנה אחרת. התוצאה היא משימות רבות המתקדמות במקביל על thread יחיד – כל קורוטינה מוותרת מרצונה על השליטה בנקודות ה-await שלה, והלולאה ממלאת את הרגעים הללו בכל קורוטינה אחרת שמוכנה להתקדם.

מתחת לפני השטח, await ו-yield משתמשים באותה תכונת זמן ריצה של Python להשהיה וחידוש של פונקציה. מילות המפתח שונות כי הקונבנציה סביבן שונה: yield מחזיר ערך לצרכן המושך עם next(); await מעביר שליטה ללולאת אירועים שמתזמנת את החידוש כאשר הפעולה שמחכים לה מוכנה. async / await הם למעשה תחביר חדש יותר לדפוס הקורוטינה – ספריות ישנות יותר בנו קורוטינות ישירות על גבי מנגנון הגנרטור, באמצעות yield from (שהוצג ב-איטרטורים וגנרטורים) כדי להאציל את ההשהיה בין קורוטינות.

2.32.4. קורוטינות זקוקות למניע

קורוטינה היא אינרטית ללא זמן ריצה שיניע אותה. הגדרת אחת היא בסדר; הרצתה זקוקה ללולאת אירועים. המודול asyncio של MicroPython מספק את לולאת האירועים הזו. החלק Asyncio מכסה כיצד להתחיל את הלולאה, לתזמן עליה קורוטינות, לשתף מצב ביניהן עם מנעולים ואירועים, לטפל בביטול ובפסקי זמן, ולעצב יישום אמיתי סביב מילות המפתח async / await שהוצגו כאן.