8.7. Events#

asyncio.Event คือ primitive การส่งสัญญาณที่ง่ายที่สุดที่โมดูลมีให้ coroutine หนึ่ง set event เมื่อบางอย่างเกิดขึ้น; coroutine อื่นๆ จำนวนเท่าใดก็ได้ รอ ให้ event ถูก set ก่อนดำเนินต่อ ไม่มี payload -- event คือ boolean ที่พลิกเป็น on และอยู่ on จนกว่าบางอย่างจะ clear มัน

8.7.1. รูปแบบพื้นฐาน#

import asyncio

async def waiter(evt):
    print("waiting")
    await evt.wait()
    print("got signal")

async def main():
    evt = asyncio.Event()
    asyncio.create_task(waiter(evt))
    await asyncio.sleep(1)
    evt.set()
    await asyncio.sleep(0)

asyncio.run(main())

งาน waiter ทำงาน, ถึง await evt.wait(), และถูกระงับ -- event เริ่มต้นในสถานะ cleared ดังนั้นการเรียก wait จะ yield กลับไปที่ loop วินาทีต่อมา main เรียก evt.set() และ waiter ถูกกำหนดให้ทำงานต่อ await asyncio.sleep(0) ท้ายนั้นเป็นแค่ yield เพื่อให้ loop มีโอกาสรัน waiter ก่อนที่ main จะคืนค่า

8.7.2. สี่เมธอด#

  • set() -- พลิก event ให้ set และกำหนดให้ทุก coroutine ที่บล็อกอยู่ที่ wait() ทำงานต่อ event ยังคง set จนกว่าจะถูก cleared

  • clear() -- พลิก event กลับไปที่ cleared coroutine ที่ await wait() หลังจากนี้จะบล็อกอีกครั้ง

  • wait() -- coroutine ถ้า event ถูก set แล้ว จะคืนค่าทันที มิฉะนั้นจะบล็อกจนกว่าบางอย่างจะ set มัน

  • is_set() -- คืนค่าสถานะปัจจุบันโดยไม่บล็อก มีประโยชน์เมื่อ coroutine ต้องการ ตรวจสอบ event โดยไม่รอมัน

8.7.3. หลาย waiter#

coroutine หลายตัวสามารถ await event เดียวกันได้ เมื่อบางอย่างเรียก set() พวกมัน ทั้งหมด ถูกกำหนดให้ทำงานต่อ event เป็น one-to-many โดยค่าเริ่มต้น ไม่จำเป็นต้องมีหลาย event เพื่อส่งสัญญาณออกไปยังหลาย waiter

8.7.4. การใช้ event ซ้ำ#

รูปแบบทั่วไปคือการใช้ event ซ้ำ -- coroutine ที่ทำงานครั้งละหนึ่งสัญญาณ -- โดยการ clear มันหลังแต่ละรอบ:

async def worker(go):
    while True:
        await go.wait()
        do_one_unit_of_work()
        go.clear()

producer จะ set event เมื่อมีงาน; worker จะ clear มันเมื่อรับสัญญาณแล้ว หาก producer อาจ set event อีกครั้งก่อนที่ worker จะตื่นขึ้น worker อาจต้องจัดการกรณีนั้น (while go.is_set(): ...) ก่อน clear

8.7.5. บริบทที่ปลอดภัย#

Event ไม่ ปลอดภัยที่จะเรียก set() จากภายใน interrupt handler กลไกที่มันใช้กำหนดให้ waiter ทำงานต่อนั้นถือว่ามันกำลังทำงานภายใน context ของ event loop เอง ซึ่ง interrupt ไม่ได้อยู่ใน context นั้น สำหรับการปลุกงาน asyncio จาก interrupt handler ThreadSafeFlag (ที่จะกล่าวถึงในไม่ช้า) คือ primitive ที่ถูกต้อง