• בלוג
  • שילוב asyncio עם Threads בפייתון

שילוב asyncio עם Threads בפייתון

הפונקציה asyncio.to_thread היא יהלום קטן שמתחבא בתיעוד של asyncio שעוזר לנו להתמודד עם הדואליות של פייתון: מצד אחד הרבה ספריות שמנצלות את asyncio כדי לכתוב קוד נקי ומהיר יותר, אבל מצד שני עדיין הרבה ספריות שכתובות בשיטה המסורתית וחוסמות את התהליכון בביצוע פעולות דיסק או תקשורת.

נדמיין שיש לנו קוד שמשתמש ב AWS כדי לתרגם מילים:

import boto3

client = boto3.client('translate')

def spanish_to_english(text):
    response = client.translate_text(
        Text=text,
        SourceLanguageCode='es',
        TargetLanguageCode='en',
    )

    return response["TranslatedText"]

ואנחנו רוצים לקרוא לו מתוך פונקציה אסינכרונית, למשל מתוך קוד טיפול בנתיב של טורנדו. אופציה אחת היא לשכתב את הקוד ולהשתמש ב aioboto3, אבל זה לא תמיד ריאלי או פשוט במיוחד כשהקוד כבר כתוב ומורכב. אופציה יותר קלה היא הפונקציה שאיתה התחלתי את הסיפור - asyncio.to_thread. הפונקציה פותחת thread חדש, מריצה בו את הקוד הסינכרוני ומחזירה קו-רוטינה שתחזיר ערך רק כשה thread יסיים. הדוגמה מהתיעוד מסבירה את הסיפור בצורה טובה:

def blocking_io():
    print(f"start blocking_io at {time.strftime('%X')}")
    # Note that time.sleep() can be replaced with any blocking
    # IO-bound operation, such as file operations.
    time.sleep(1)
    print(f"blocking_io complete at {time.strftime('%X')}")

async def main():
    print(f"started main at {time.strftime('%X')}")

    await asyncio.gather(
        asyncio.to_thread(blocking_io),
        asyncio.sleep(1))

    print(f"finished main at {time.strftime('%X')}")


asyncio.run(main())

קריאה רגילה ל blocking_io היתה חוסמת את ה Event Loop וגורמת להמתנה כוללת של שתי שניות. בזכות השימוש ב to_thread, הפונקציה נקראת בתהליכון נפרד וה sleep מתבצע במקביל ל asyncio.sleep, כך שסך הכל התוכנית ממתינה שניה אחת לשתי הפעולות.

הפונקציה נכנסה לשפה בפייתון 3.9 ואתם יכולים למצוא עליה מידע נוסף יחד עם כל הפרטים הקטנים בדף התיעוד: https://docs.python.org/3/library/asyncio-task.html#running-in-threads