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 จนกว่าจะถูก clearedclear()-- พลิก 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 ที่ถูกต้อง