5.2 · البرمجة غير المتزامنة (asynchronous programming) (asyncio)

المستوى 5 · المستوى الاحترافي والتصنيع (industrialization)

5.2البرمجة غير المتزامنة (asynchronous programming) (asyncio)

الهدف: تدبير التزامن مع async/await و asyncio.
الوقت المقدّر: 15 دقيقة · قراءة ذاتية · إذا كان لديك 5 دقائق فقط: اقرأ «الأساسي في 30 ثانية» + المخطط.

الأساسي في 30 ثانية

  • غير المتزامن (Async) = استغلال الانتظار : بينما ينتظر (API، قاعدة، ملفات)، يفعل البرنامج شيئا آخر بدل البقاء محجوبا.
  • الثلاثي : async def يصرح، وawait ينتظر دون حجب، وgather يطلق معا — وasyncio.run في القمة.
  • عوالم منفصلة : المحجوب (requests، sleep) يشل غير المتزامن — مكتبات متوافقة مع async أو لا شيء.

حالة مصغرة متسلسلة — نبيل يسرع جمعه في الجزائر

نبيل، محلل بيانات مبتدئ في الجزائر، يجمع كل صباح بيانات من عدة مصادر وب : بالتسلسل، ينتظر سكريبته كل جواب واحدا بعد الآخر ويدوم الجمع أبدا. أعاد كتابة الحلقة كروتينات (coroutines) مطلقة معا مع asyncio.gather : تتداخل الانتظارات بدل أن تتجمع. تتمة الحالة 5.1 — يشغل نبيل الأوقات الميتة بدل تحملها.

تتمة الحالة 5.1 — يمر نبيل من التسلسلي الذي ينتظر إلى المتزامن الذي يغطي.

1. حين يهيمن الانتظار : غير المتزامن

API، وقاعدة بيانات، وملفات بعيدة : ينتظر البرنامج أكثر مما يحسب. تحرر البرمجة غير المتزامنة الخيط أثناء الانتظار : بدل البقاء محجوبا، يمر إلى مهام أخرى، وخيط واحد يدير آلاف الطلبات المتزامنة دون آلاف الخيوط. يجد نبيل فيها جمعه الصباحي : العنق هو انتظار الشبكة، لا الحساب.

حد واضح : لحساب خالص (مقيد بالمعالج CPU-bound)، لا يسرع غير المتزامن شيئا — إنه multiprocessing. غير المتزامن لـ I/O، و multiprocessing لـ CPU : الاختيار حسب العنق، لا حسب الموضة.

2. الثلاثي : async def و await و gather

async def fetch(url) يصرح بروتين (coroutine)، وawait ينتظر دون حجب (يُحرَّر الخيط لمهام أخرى)، وasyncio.gather(*taches) يطلق معا وينتظر الجميع. وasyncio.run(main()) ينظم من القمة عبر حلقة الأحداث. أطلق نبيل جمعه في gather : ثلاثة انتظارات تتداخل بدل أن تتجمع.

الفخ الكلاسيكي : نسيان await يجعل روتينا (coroutine) لا يُنفَّذ أبدا (تحذير خفي، ونتيجة فارغة). كل روتين مُنشأ يجب انتظاره أو تجميعه.

3. عدم خلط العوالم

المحجوب في غير المتزامن يشل كل شيء : يجمد time.sleep الخيط، ويلغي requests (المتزامن) الفائدة — استعملوا asyncio.sleep ومكتبات متوافقة مع async (httpx، aiohttp). بقية محجوبة ناجية تُعزَل عبر run_in_executor. استبدل نبيل requests بعميل async : دون ذلك، كان gather ينتظر مع ذلك.

القاعدة الذهبية : غير متزامن من الطرف إلى الطرف أو لا شيء — المختلط هو أسوأ العوالم. ونقيس قبل/بعد لإثبات المكسب بدل افتراضه.

خطأ شائع في الفهم: « غير المتزامن يجعل كل شيء أسرع، و requests تشتغل فيه ». لا. لا يسرع غير المتزامن إلا الأحمال التي يهيمن عليها الانتظار (مقيدة بـ I/O) ؛ وفي الحساب الخالص، يعقد دون مكسب. و requests، المتزامنة والمحجوبة، تشل الحلقة : في غير المتزامن، نستعمل مكتبات متوافقة مع async (httpx/aiohttp) — وإلا دفعنا التعقيد دون لمس المكسب.
الانتظار معا صرح، وانتظر دون حجب، وأطلق معا async def التصريحبالروتين (coroutine)await الانتظار دون حجبgather الإطلاق معا
صرح، وانتظر دون حجب، وأطلق معا
نص المخطط (SEO / قارئات الشاشة / الترجمة): 1) async def : يصرح نبيل بجمع بياناته في الجزائر كروتينات (coroutines). 2) await : كل انتظار شبكة يحرر الخيط بدل حجبه. 3) gather : تنطلق كل الجموع معا — تتداخل الانتظارات. الدرس : مكتبات متوافقة مع async من الطرف إلى الطرف، وقياس المكسب قبل/بعد.

هل تعلم ؟

نسيان await يخلق روتينا شبحا : يوجد لكن لا يُنفَّذ أبدا — يحذر بايثون (Python) بتحذير خفي يتجاهله كثيرون.
في غير المتزامن، النسيان لا يعطل : لا يحدث شيء.

روتين مُنشأ، ولم يُنتظَر أبدا : عمل موعود، ولم يُنجَز أبدا.

🔎 مثال ملموس — يجمع نبيل مصادره بالتوازي

تتداخل الانتظارات بدل أن تتجمع.

import asyncio

async def collecter(source):
    print(f"collecte {source}...")
    await asyncio.sleep(1)
    return f"{source} : ok"

async def main(sources):
    return await asyncio.gather(*(collecter(s) for s in sources))

res = asyncio.run(main(["api", "fichier", "base"]))
print(res)

القراءة : ثلاثة انتظارات ثانية واحدة تستغرق ثانية واحدة تقريبا في المجموع، لا ثلاثا.

الدرس : في إنتاج الشبكة، استبدلوا الـ sleep بعميل async (httpx/aiohttp).

مفردات القسم

البرمجة غير المتزامنة
نموذج يشغل البرنامج أثناء أوقات الانتظار بدل حجبه.

مثال بسيط: يجمع نبيل ثلاثة مصادر معا بدل انتظار كل واحد.

ما ليس كذلك: عدة معالجات تحسب بالتوازي.

رد فعل: هل ينتظر برنامجي أكثر مما يحسب ؟

async / await
يصرح async بروتين (coroutine) ؛ وينتظر await دون حجب الخيط.

مثال بسيط: ينتظر نبيل (await) كل جمع لتحرير الخيط.

ما ليس كذلك: نداء محجوب يجمد كل شيء.

رد فعل: هل نسيت await (روتين شبح) ؟

asyncio.gather
يطلق عدة روتينات (coroutines) معا وينتظر الجميع.

مثال بسيط: جمع نبيل جموعه الثلاثة في gather واحد.

ما ليس كذلك: حلقة تنتظر كل مهمة واحدة بعد الأخرى.

رد فعل: هل يمكن أن تتداخل هذه الانتظارات ؟

الروتين (Coroutine)
دالة async قابلة للتعليق والاستئناف من حلقة الأحداث.

مثال بسيط: دالة الجمع عند نبيل، المعلقة أثناء الـ sleep.

ما ليس كذلك: دالة عادية تحجب حتى النهاية.

رد فعل: من ينتظر هذا الروتين ؟

مقيد بـ I/O مقابل مقيد بالمعالج
الانتظار (شبكة/قرص) ← asyncio ؛ والحساب المكثف ← multiprocessing.

مثال بسيط: أخذ نبيل asyncio لواجهات API، لا لحساب ضخم.

ما ليس كذلك: async لتسريع حساب خالص.

رد فعل: هل عنقي الانتظار أم الحساب ؟

في التطبيق — اكتشاف async/await مع asyncio

  1. اكتبوا روتينا (coroutine) « async def tache(n) » يعرض رسالة، وينتظر (await asyncio.sleep(1)) ثم يعرض النهاية.
  2. أطلقوه مع asyncio.run() ولاحظوا السلوك التسلسلي لمهمة واحدة.
  3. أطلقوا عدة مهام بالتوازي مع asyncio.gather() : لاحظوا أن 3 انتظارات 1 ث تأخذ ~1 ث في المجموع، لا 3.
  4. استنتجوا : يلمع غير المتزامن حين ننتظر (شبكة، I/O)، لا لتسريع حساب مكثف.
تفهمون async/await و asyncio، وتعرفون متى يجلب غير المتزامن مكسبا حقيقيا (انتظارات I/O) : مهارة متقدمة مطلوبة.

نص جاهز للقول — تبرير غير المتزامن

« في الجزائر، جمعنا الصباحي ينتظر كل مصدر واحدا بعد الآخر : العنق هو انتظار الشبكة، لا الحساب. أقترح تمرير الحلقة إلى روتينات (coroutines) مع gather وعميل async : ستتداخل الانتظارات. أقيس قبل/بعد لإثبات المكسب، وأبقي الحساب الخالص خارج غير المتزامن. »

يُقال للمسؤول لتمرير إعادة كتابة مستهدفة ومقاسة، دون لمس الحساب.

نموذج نسخ-لصق — جمع async عام

import asyncio

async def tache(nom):
    print(f"debut {nom}")
    await asyncio.sleep(1)
    print(f"fin {nom}")
    return nom

async def main():
    return await asyncio.gather(*(tache(n) for n in ["a", "b", "c"]))

if __name__ == "__main__":
    print(asyncio.run(main()))
# في إنتاج الشبكة : استبدال asyncio.sleep بـ httpx/aiohttp (متوافقة مع async).

مستقل / ميزانية صغيرة

نبيل وحده : يكفي gather على مصادره الموجودة — دون بنية جديدة، فقط روتينات (coroutines).

مقاولة صغيرة / فريق مصغر

عميل HTTP غير متزامن (async) مشترك : يجمع الفريق كله دون مضاعفة الجلسات المحجوبة.

مقاولة متوسطة في الجزائر

يقيس نبيل قبل/بعد في الجزائر على الجمع الحقيقي : المكسب المثبت يبرر إعادة الكتابة.

فريق يبدأ

ثنائي : أحدهما يحول مصدرا إلى async، والآخر يتعقب النداءات المحجوبة الباقية.

✓ ممارسات جيدة

  • غير المتزامن لـ I/O، و multiprocessing للمعالج : الاختيار حسب العنق.
  • gather للإطلاق معا ؛ ومكتبات متوافقة مع async (httpx).
  • القياس قبل/بعد : إثبات المكسب بدل افتراضه.

✗ أخطاء شائعة

  • نداء requests (المحجوب) في غير المتزامن.
  • نسيان await : روتين شبح لا يُنفَّذ أبدا.
  • وضع async على حساب خالص (لا مكسب، وتعقيد مهدى).

النقاط الأساسية

  • يشغل غير المتزامن البرنامج أثناء أوقات الانتظار بدل حجبه.
  • يعرّف async روتينا (coroutine) ؛ ويعلق await منتظرا نتيجة.
  • يطلق asyncio.gather() عدة مهام انتظار بالتوازي.
  • يخدم asyncio حالات I/O-bound (شبكة، قرص)، لا الحسابات المكثفة (← multiprocessing).

تحدي 48 ساعة — طبق غدا

حولوا حلقة انتظار حقيقية واحدة (API، ملفات) إلى gather : هل تأخذ الانتظارات الثلاثة 3 أضعاف الوقت بعد ؟

قائمة الخروج من القسم
  • أستطيع شرح async/await/gather في جملة واحدة.
  • عرض gather عندي يغطي الانتظارات (مثبت بالقياس).
  • أعرف لماذا requests محظور في غير المتزامن.
تقييم ذاتي 1-5 : هل ميزت الانتظار والحساب قبل الترميز ؟هل بقي نداء محجوب في سلسلتي غير المتزامنة ؟

أسئلة شائعة

غير المتزامن والخيوط والعمليات المتعددة : ما الفرق ؟

ثلاثة أجوبة لثلاثة اختناقات. يدير asyncio تزامن انتظارات I/O في خيط واحد (مثالي شبكة/قرص). ويتقاسم الخيوط (threading) أيضا عملية واحدة لكن يناسب بعض I/O المحجوبة. ويطلق تعدد العمليات (multiprocessing) عدة عمليات لموازاة الحساب على عدة أنوية. نختار حسب ما إذا كان العنق الانتظار (asyncio) أو الحساب (multiprocessing).

هل يجعل غير المتزامن البرنامج أسرع حتما ؟

لا. يسرع الأحمال التي يهيمن عليها الانتظار (كثير طلبات شبكة متزامنة). لحساب خالص (مقيد بالمعالج CPU-bound)، لا يجلب شيئا، بل قد يعقد الشيفرة دون داع. استعملوه في محله، حيث توجد أوقات ميتة لاستغلالها — وقيسوا لإثباته.

تحقّق من فهمك

في أي حالة يجلب asyncio مكسبا ؟

موارد إضافية (اختياري، للتعمّق أكثر)

اختبار المستوى 5أسئلة جديدة وأصعب للتحقق من مكتسباتك