8.7. الأحداث

asyncio.Event هي أبسط بدائية إشارة توفرها الوحدة. تضبط كوروتين واحدة حدثًا عندما يقع شيء ما؛ وينتظر أي عدد من الكوروتينات الأخرى ضبط الحدث قبل المتابعة. ولا توجد حمولة -- فالحدث قيمة منطقية تنقلب إلى التشغيل وتبقى مشغّلة حتى يمحوها شيء ما.

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())

تعمل مهمة المنتظِر، وتصل إلى await evt.wait()، وتُعلَّق -- إذ يبدأ الحدث في الحالة الممحوة، فيتنازل استدعاء wait للحلقة. وبعد ثانية يستدعي main الأمر evt.set() فتُجدول مهمة المنتظِر للاستئناف. أما await asyncio.sleep(0) الأخيرة فهي مجرد تنازل كي تحصل الحلقة على فرصة لتشغيل المنتظِر قبل أن يعود main.

8.7.2. الطرق الأربع

  • set() -- تقلب الحدث إلى الحالة المضبوطة، وتجدول كل كوروتين محجوبة حاليًا على wait() للاستئناف. ويبقى الحدث مضبوطًا حتى يُمحى.

  • clear() -- تعيد قلب الحدث إلى الحالة الممحوة. والكوروتينات التي تنتظر بـ await wait() بعد ذلك ستُحجب مجددًا.

  • wait() -- كوروتين. إذا كان الحدث مضبوطًا بالفعل، تعود فورًا. وإلا تُحجب حتى يضبطه شيء ما.

  • is_set() -- تعيد الحالة الراهنة دون حجب. مفيدة عندما تريد كوروتين فحص الحدث دون انتظاره.

8.7.3. المنتظِرون المتعددون

يمكن لعدة كوروتينات أن تنتظر الحدث نفسه بـ await. وعندما يستدعي شيء ما set() تُجدول جميعها للاستئناف. والحدث واحد-إلى-متعدد افتراضيًا؛ فلا حاجة لعدة أحداث للتفرع إلى عدة منتظِرين.

8.7.4. إعادة استخدام حدث

نمط شائع هو استخدام حدث بشكل متكرر -- كوروتين تعمل مرة واحدة لكل إشارة -- عبر محوه بعد كل تمريرة:

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

يضبط المنتِج الحدث كلما كان هناك عمل؛ ويمحوه العامل بمجرد أن يلتقط الإشارة. وإذا كان المنتِج قد يضبط الحدث مجددًا قبل أن يستيقظ العامل، فقد يرغب العامل في معالجة تلك الحالة (while go.is_set(): ...) قبل المحو.

8.7.5. السياقات الآمنة

Event ليست آمنة لاستدعاء set() عليها من داخل معالج مقاطعة. فالآلية التي تستخدمها لجدولة المنتظِرين تفترض أنها تعمل داخل سياق حلقة الأحداث نفسها، وهو ما لا تكونه المقاطعات. ولإيقاظ مهمة asyncio من معالج مقاطعة، تكون ThreadSafeFlag (التي تُغطى قريبًا) هي البدائية الصحيحة.