8.10. الأصناف القابلة للانتظار

8.10.1. ما هو الكائن القابل للانتظار

عندما تكتب الكوروتين await x، تسأل اللغة x كيف تنتظره. أي كائن يعرف كيف يجيب يكون قابلًا للانتظار. وهناك نوعان:

  • كائنات الكوروتين التي تعيدها دوال async def. استدعاء send_request() ينتج واحدًا من هذه في كل مرة -- كائن الكوروتين، وليس نتيجة الدالة. كتابة await send_request() هي ما يشغّل الجسم فعليًا.

  • كائنات صنف يعرّف الطريقة __await__. إن async def هي الاختصار للنوع الأول؛ و __await__ هي الطريقة اليدوية لصنع النوع الثاني.

تستخدم شيفرة التطبيق النوع الأول افتراضيًا. أما النوع الثاني فيوجد للحالة النادرة التي يجب فيها أن يكون الكائن نفسه هو ما ينتظره المستدعي بـ await، وليس نتيجة استدعاء طريقة عليه.

8.10.2. الشكلان جنبًا إلى جنب

المنطق نفسه مكتوبًا ككوروتين وكصنف قابل للانتظار:

import asyncio

# async def -- almost always the right choice
async def yield_then_return(value):
    await asyncio.sleep_ms(0)
    return value

# awaitable class -- equivalent, rarely written
class YieldThenReturn:
    def __init__(self, value):
        self._value = value

    def __await__(self):
        yield                       # let the loop run once
        return self._value          # value of the ``await`` expression

يمكن انتظار كليهما بـ await بالطريقة نفسها:

async def main():
    a = await yield_then_return(1)
    b = await YieldThenReturn(2)
    print(a, b)                     # prints: 1 2

الشكل async def أقصر، ويُقرأ مثل Python العادية، ويترك للغة إدارة كل مسك دفاتر التعليق والاستئناف. ولا يحصل شكل الصنف على أي شيء إضافي في هذا المثال.

8.10.3. متى يستحق شكل الصنف مكانته

عندما يجب أن يكون الكائن شيئًا يتداوله التطبيق -- لا دالةً يستدعيها المستدعي للحصول على كوروتين -- يكون شكل الصنف هو الخيار الوحيد:

  • قيمة شبيهة بالمستقبل ينشئها التطبيق ويسلّمها لمنتِج وينتظرها بـ await لاحقًا لاسترداد النتيجة المنتَجة.

  • بدائية مخصّصة مبنية فوق أخرى موجودة حيث تكون الواجهة البرمجية الطبيعية await my_thing بدلًا من await my_thing.wait(). وصنف asyncio.Task نفسه مثال مدمج -- إذ تنجح كتابة await task لأن Task يعرّف __await__.

بالنسبة لأي شيء يناسب الشكل استدعِ دالة، احصل على كوروتين، انتظرها، يفوز async def.

8.10.4. تفاصيل البروتوكول

__await__ هي طريقة عادية (وليست async def) تعيد مكرِّرًا. وكتابتها كمولّد -- بتضمين yield واحدة على الأقل في الجسم -- يجعلها كذلك تلقائيًا. كل yield تعلّق الكوروتين التي تنتظر الكائن بـ await وتعيد التحكم إلى حلقة الأحداث؛ ويستأنف المولّد في المرة التالية التي تجدول فيها الحلقة المهمة. أما return المجردة (أو الوصول إلى النهاية) فتُنهي الانتظار؛ وأيًا كان ما تنتجه return فإنه يصبح قيمة تعبير await.

في الممارسة العملية، فإن الصنف النادر الذي يفعل ذلك يفوّض التنازل إلى كائن قابل للانتظار موجود بدلًا من قيادة الحلقة بنفسه:

class WaitForEvent:
    def __init__(self, event):
        self._event = event

    def __await__(self):
        yield from self._event.wait().__await__()
        return self._event

إن yield from تعيد استخدام __await__ الخاصة بالحدث الأساسي للتعليق الفعلي. ومعظم تطبيقات asyncio لا تحتاج أبدًا إلى كتابة أي من الشكلين.