- تتيح البرمجة غير المتزامنة في بايثون إمكانية تقدم مهام متعددة تعتمد على الإدخال/الإخراج دون أن تعيق بعضها البعض.
async,awaitوحلقة الأحداث. - يمكنك استخدام أدوات مثل
asyncio,aiohttpتتيح أدوات إدارة السياق غير المتزامنة والتكرار غير المتزامن إمكانية إنشاء شبكات قابلة للتوسع وأحمال عمل كثيفة تعتمد على واجهات برمجة التطبيقات. - تتفوق المعالجة غير المتزامنة في عمليات الإدخال والإخراج للشبكة والملفات، ولكن يجب استكمالها بالمعالجة المتعددة أو الخدمات المتخصصة للمهام التي تعتمد على وحدة المعالجة المركزية.
- تُعد الممارسات الجيدة - تجنب المكالمات المحظورة، والحد من التزامن، ومعالجة الأخطاء لكل مهمة - أساسية لكتابة تطبيقات غير متزامنة موثوقة.

لقد تحولت البرمجة غير المتزامنة في بايثون من كونها موضوعًا متخصصًا إلى واحدة من المهارات الأساسية لأي شخص يقوم ببناء تطبيقات حديثة وسريعة الاستجابة. إذا كنت تعمل مع واجهات برمجة تطبيقات الويب، أو الخدمات المصغرة، أو لوحات المعلومات الآنية، أو أي نوع من عمليات الإدخال/الإخراج المكثفة، فربما تكون قد واجهت مشكلة حيث يقضي برنامجك وقتًا أطول في الانتظار بدلًا من إنجاز العمل الفعلي. وهنا تحديدًا تبرز أهمية تقنيات البرمجة غير المتزامنة.
بدلاً من ترك برنامجك في وضع الخمول أثناء انتظار الشبكة أو القرص أو خدمة خارجية، فإن التعليمات البرمجية غير المتزامنة تسمح لك بتداخل فترات الانتظار هذه والحفاظ على استمرار عمل التطبيق. سنتعمق في هذا الدليل في كيفية عمل البرمجة غير المتزامنة في بايثون، والمشاكل التي تحلها، ومتى تكون مفيدة حقًا ومتى تكون غير مناسبة، وسنستعرض أمثلة عملية باستخدامها. async, await, asyncio والمكتبات غير المتزامنة الشائعة مثل aiohttp.
ما هي البرمجة غير المتزامنة في بايثون؟
في جوهرها، البرمجة غير المتزامنة هي طريقة لهيكلة التعليمات البرمجية الخاصة بك بحيث يمكن للمهام المتعددة إحراز تقدم دون أن تعيق بعضها البعض، حتى عندما تشترك في مؤشر ترابط واحد لنظام التشغيل. في أسلوب البرمجة المتزامنة التقليدي، تنتهي كل عملية قبل أن تبدأ العملية التالية: استدعاء واجهة برمجة التطبيقات، الانتظار، تحليل الاستجابة، ثم الانتقال إلى العملية التالية. أما في البرمجة غير المتزامنة، فيمكنك تشغيل عدة عمليات طويلة الأمد والسماح لـ Python بالتبديل بينها عندما تكون إحداها في حالة انتظار.
تُنفذ لغة بايثون هذا النموذج من خلال مزيج من بناء الجملة الخاص وجدول زمني تعاوني مبني حول حلقة أحداث. الكلمتان المفتاحيتان اللتان تفتحان كل هذا هما async و awaitيمكنك تحديد الدوال على أنها غير متزامنة باستخدام async defوتتوقف داخلها مع await كلما وصلت إلى عملية يمكن أن تعيد التحكم إلى حلقة الأحداث.
An async def لا تُرجع الدالة قيمة مباشرة؛ بل تُرجع كائن روتين فرعي يمثل عملية حسابية يمكن جدولتها وانتظارها. عند استخدام await داخل تلك الدالة، يقوم بايثون بتعليق الروتين الفرعي الحالي ويسمح للمهام الأخرى المعلقة بالعمل حتى تكتمل العملية المنتظرة (مثل طلب الشبكة)، وعندها يستأنف التنفيذ مباشرة بعد ذلك. await.
هذا أمر بالغ الأهمية: عادةً ما يكون كود بايثون غير المتزامن أحادي الخيوط، ولكنه متزامن بمعنى أن العمليات المتعددة تتقدم في نوافذ زمنية متداخلة. بينما تنتظر إحدى المهام إتمام عمليات الإدخال/الإخراج، تحصل مهمة أخرى على وقت المعالج. لهذا السبب، يُعدّ البرمجة غير المتزامنة مثالية لأحمال العمل التي تعتمد بشكل كبير على عمليات الإدخال/الإخراج، لكنها لا تُسرّع بشكل سحري العمليات التي تتطلب موارد معالج عالية.
تشبيه ملموس: معارض الشطرنج ووقت الانتظار
يأتي أحد التشبيهات الكلاسيكية المستخدمة في مجتمع بايثون لشرح التزامن مقابل التنفيذ التسلسلي من مباراة شطرنج متزامنة. تخيل أستاذة كبيرة تلعب ضد 24 لاعبًا هاويًا. يمكنها إدارة الحدث بطريقتين مختلفتين، تعكسان الاستراتيجيات المتزامنة وغير المتزامنة.
في النسخة المتزامنة، تجلس مع خصم واحد وتلعب تلك اللعبة الواحدة من البداية إلى النهاية قبل الانتقال إلى الطاولة التالية. تستغرق كل حركة تقوم بها 5 ثوانٍ، بينما يقضي كل لاعب هاوٍ حوالي 55 ثانية في التفكير. تتضمن المباراة النموذجية 30 تبادلًا للحركات (أي 60 حركة إجمالًا). هذا يعني أن كل مباراة تستغرق (55 + 5) × 30 = 1800 ثانية، أي حوالي 30 دقيقة. ومع 24 مباراة، يستمر الحدث بأكمله لمدة 12 ساعة.
في النسخة غير المتزامنة، تتجول في الغرفة وتقوم بحركة واحدة على كل لوحة، ثم تنتقل فوراً إلى اللوحة التالية بينما يفكر الخصم الحالي في رده. تستغرق جولة واحدة من الحركات على 24 لوحة 120 ثانية، أي دقيقتين. بعد 30 جولة من هذا النوع، تُستكمل المجموعة الكاملة من الألعاب في حوالي 3600 ثانية، أي ساعة واحدة.
الخلاصة المهمة هي أن سرعة لعبها الخام لم تتغير أبدًا؛ ما تغير هو كيفية استغلالها لوقت انتظار الخصوم. يتبع كود بايثون غير المتزامن نفس المبدأ: فهو لا يجعل الإدخال/الإخراج أسرع، ولكنه يضمن أنك تقوم بشيء مفيد بينما قد تكون عالقًا في انتظار الشبكة أو القرص أو أي مورد خارجي.
الطلبات المتزامنة مقابل الطلبات غير المتزامنة: مثال واقعي باستخدام واجهات برمجة التطبيقات
إحدى أكثر حالات الاستخدام شيوعًا للبرمجة غير المتزامنة في بايثون هي التعامل مع واجهات برمجة التطبيقات الخارجية، حيث يمكن أن يستغرق كل طلب بسهولة مئات المللي ثانية أو أكثر. على سبيل المثال، تخيل أنك تريد الحصول على عدد المتابعين لعدة حسابات على GitHub باستخدام واجهة برمجة التطبيقات العامة الخاصة بها.
يعتمد النهج المتزامن المباشر على استخدام عميل HTTP شائع يعمل بتقنية الحظر مثل requests. ستقوم بتنفيذ طلب GET لكل نقطة نهاية مستخدم في حلقة تكرارية، وقراءة حمولة JSON، واستخراج followers قم بمعالجة البيانات وطباعتها أو تخزينها. هذه الطريقة بسيطة وسهلة القراءة، ولكن لها عيب: فمع كل حساب تقوم بمعالجته، يُنفذ البرنامج الطلب ثم ينتظر الرد قبل البدء بالحساب التالي.
لذا إذا قمت بفحص ثلاثة مستخدمين مثل api.github.com/users/python, api.github.com/users/google و api.github.com/users/firebaseيقوم الكود بإرسال الطلب الأول، ثم يتوقف حتى يستجيب GitHub، ثم ينتقل إلى الطلب الثاني، وهكذا. قد يكون هذا مقبولاً مع عدد قليل من المستخدمين، ولكن مع نمو قائمتك إلى المئات أو الآلاف، يتضخم إجمالي وقت المعالجة، لأن تطبيقك يقضي معظم فترة حياته في وضع الخمول، في انتظار الخادم البعيد.
لتسريع الأمور، يمكنك التبديل إلى تطبيق غير متزامن مبني على أساس asyncio وعميل HTTP يدعم العمليات غير المتزامنة مثل aiohttp. في هذا النموذج، يتم تشغيل عدة مهام روتينية فرعية تُرسل جميعها طلبات HTTP الخاصة بها في وقت واحد تقريبًا. ثم تنتظر حلقة الأحداث استجابات من أي منها، وتستأنف كل مهمة عند وصول البيانات، بدلاً من انتظار اكتمال طلب واحد بالكامل قبل بدء الطلب التالي.
عند مقارنة هذين النهجين جنبًا إلى جنب، عادةً ما يفوز الإصدار غير المتزامن بفارق كبير، خاصة مع زيادة عدد المستخدمين. لا يتغير الوقت المستغرق لكل طلب، ولكن إجمالي الوقت اللازم للحصول على جميع النتائج ينخفض بشكل حاد لأنك تتعامل مع العديد من الاتصالات في وقت واحد بدلاً من التعامل معها بشكل متسلسل.
المفاهيم الأساسية: الروتينات الفرعية، حلقة الأحداث، المهام، والمستقبلات
في جوهرها، تعتمد لغة بايثون غير المتزامنة الحديثة على عدد قليل من اللبنات الأساسية التي توفرها بشكل رئيسي... asyncio وحدة. إن فهم هذه المفاهيم سيجعل بقية النظام البيئي أقل غموضًا بكثير وسيساعدك على تصميم بنى غير متزامنة قوية.
الروتين الفرعي هو نوع خاص من الدوال التي يمكنها إيقاف تنفيذها مؤقتًا واستئنافه. في قواعد اللغة الحالية، يمكنك تعريف واحد باستخدام async defعند استدعاء هذه الدالة، تحصل على كائن روتيني فرعي يحتاج إلى انتظار أو جدولة؛ فهو لا يُنفذ حتى الاكتمال فورًا مثل الدالة العادية. في الداخل، كلما استخدمت await في حالة الانتظار (روتين فرعي آخر، أو مهمة، أو مستقبل، وما إلى ذلك)، يقوم بايثون بتعليق هذا الروتين الفرعي حتى تنتهي العملية المنتظرة.
حلقة الأحداث هي المنسق الذي يتتبع جميع العمليات الفرعية المعلقة وعمليات الإدخال/الإخراج والمؤقتات، ويقرر أي جزء من التعليمات البرمجية يتم تشغيله في أي وقت معين. تاريخياً، كان عليك الحصول على الحلقة وإدارتها بشكل صريح عبر asyncio.get_event_loop()لكن في كود بايثون الحديث، النمط المفضل هو let asyncio.run() قم بإنشاء وتشغيل وإغلاق الحلقة نيابةً عنك حول دالة غير متزامنة من المستوى الأعلى مثل main().
المهام عبارة عن أغلفة حول الروتينات الفرعية التي تخبر حلقة الأحداث بجدولة تنفيذها. يمكنك اعتبارها مهامًا خفيفة الوزن: إذ يمكن للحلقة أن تُدخل التقدم بين العديد من المهام دون تشغيل خيوط متعددة. عادةً ما تقوم بإنشاء المهام باستخدام asyncio.create_task() أو عن طريق الاتصال بمساعدين مثل asyncio.gather()والتي تدير داخلياً مجموعة من المهام.
تمثل القيم المستقبلية نتائج ستصبح متاحة لاحقًا، على غرار الوعود في جافا سكريبت. كل من المهام والمستقبلات عبارة عن كائنات قابلة للانتظار: يمكنك await يُعلّق هذا البروتوكول الموحد العمليات حتى تنتهي العملية الأساسية. يُبسّط هذا البروتوكول الموحد شيفرة التنسيق بشكل كبير، لأن تكوين التدفقات غير المتزامنة يقتصر على انتظار الكائنات الصحيحة بالترتيب الصحيح.
تطبيق بناء الجملة غير المتزامن عمليًا: async, await, async with و async for
استخدم async لا تقتصر الكلمة المفتاحية على تعريفات الوظائف؛ بل تمتد أيضًا إلى مديري السياق والحلقات بحيث يمكن للأنماط الأكثر تقدمًا المشاركة في عالم البرمجة غير المتزامنة. إن معرفة هذا التركيب الموسع يساعدك على كتابة تعليمات برمجية أنيقة حول اتصالات الشبكة والجلسات والتدفقات والبروتوكولات المخصصة.
الشكل الأكثر شيوعاً هو async def، والذي يحدد دالة غير متزامنة (مصنع روتيني فرعي). داخل هذه الوظيفة، ستستخدم بكثرة await عند استدعاء أي عملية فرعية أخرى أو عملية قابلة للانتظار، مثل asyncio.sleep()، طلب HTTP غير متزامن، أو استعلام قاعدة بيانات غير متزامن. لاحظ أنه لا يمكنك استخدام await يجب أن يكون موجودًا مباشرةً في المستوى الأعلى من البرنامج النصي؛ يجب أن يكون موجودًا داخل async def.
قد تشعر برغبة في الاتصال time.sleep() داخل روتينك الفرعي للتأخيرات، من شأن ذلك أن يهزم تمامًا الغرض من استخدام البرمجة غير المتزامنة. time.sleep() يؤدي ذلك إلى حظر الخيط بأكمله، بما في ذلك حلقة الأحداث، لذا لا يمكن لأي مهمة غير متزامنة أخرى أن تتقدم خلال تلك الفترة. بدلاً من ذلك، يجب عليك استخدام النظير غير المحظور. asyncio.sleep()، مما يعيد التحكم إلى الحلقة أثناء العد التنازلي للمؤقت.
يدعم بايثون أيضًا مديري السياق غير المتزامن عبر async with، يتم تنفيذها من خلال تحديد الأساليب الخاصة __aenter__ و __aexit__. يُعد هذا مفيدًا بشكل خاص عند التعامل مع الكائنات التي تتطلب إعدادًا وتفكيكًا سلسين يتضمنان عمليات غير متزامنة، مثل فتح جلسة شبكة أو الحصول على مورد غير متزامن. ومن الأمثلة الشائعة على ذلك إدارة aiohttp.ClientSession أو طلب HTTP فردي باستخدام async with بدلاً من استدعاء الكتل يدويًا close().
وأخيرًا، يتم عرض التكرار غير المتزامن من خلال async for، والتي تعتمد على الأساليب السحرية __aiter__ و __anext__ كما هو موضح في PEP 492. تتيح لك المكررات غير المتزامنة والمولدات غير المتزامنة إمكانية إرجاع العناصر بمرور الوقت باستخدام await داخل عملية التكرار، وهو أمر مثالي لبث البيانات التي تصل تدريجياً عبر الشبكة أو من مصدر غير متزامن آخر.
تشغيل مهام متعددة في وقت واحد مع asyncio
تظهر القوة الحقيقية للبرمجة غير المتزامنة عندما تقوم بتشغيل عدة مهام مرتبطة بالإدخال/الإخراج في وقت واحد بدلاً من تشغيلها واحدة تلو الأخرى. في بيئة بايثون غير المتزامنة، الأدوات الرئيسية لذلك هي asyncio.create_task() و asyncio.gather()وكلاهما يقوم بجدولة العمليات الفرعية على حلقة الأحداث.
مع asyncio.gather()يمكنك بدء عدة إجراءات فرعية دفعة واحدة والانتظار حتى تنتهي جميعها، واستلام نتائجها كقائمة أو مجموعة. هذا شائع للغاية مع مجموعات من استدعاءات HTTP، أو استعلامات قواعد البيانات، أو أي عملية غير متزامنة متكررة. في الخلفية، gather() يغلف كل روتين فرعي في مهمة ويضمن إنجازها جميعًا.
إذا عدت إلى مثال جلب ملفات تعريف GitHub ولكن قمت بإعادة هيكلته باستخدام aiohttp و asyncio.gather()سينتهي بك الأمر بثلاث استدعاءات لدالة مثل fetch_user() يتم إطلاقها في وقت واحد. تبدأ كل مهمة طلب HTTP الخاص بها، ثم تتخلى عن التحكم أثناء انتظار البيانات، ثم تستأنف تحليل الاستجابة عند وصولها. من وجهة نظر المستخدم، تظهر النتائج الثلاث جميعها في نفس الوقت تقريبًا.
ومع ذلك، هناك حالات لا ترغب فيها بتشغيل آلاف أو ملايين المهام دفعة واحدة، لأن ذلك قد يرهق جهازك أو يتجاوز حدود المعدل الخارجية. يتمثل أحد الأنماط الشائعة في الحد من التزامن عن طريق معالجة فقط MAX_TASKS العمليات في وقت واحد، إما باستخدام الإشارات، أو مجموعات محدودة، أو منطق التجميع اليدوي داخل سير العمل غير المتزامن الخاص بك.
جانب آخر بالغ الأهمية عند تشغيل العديد من المهام في وقت واحد هو كيفية التعامل مع الأخطاء؛ فترك طلب واحد فاشل يتسبب في تعطل الدفعة بأكملها نادرًا ما يكون مقبولًا في التطبيقات الحقيقية. من الناحية المثالية، يجب أن يقوم التنسيق غير المتزامن الخاص بك بالتقاط وإدارة الاستثناءات لكل مهمة، وربما تسجيلها، وإعادة المحاولة بشكل انتقائي أو إرجاع نتائج جزئية مع الحفاظ على بقية الدفعة سليمة.
التعامل مع التزامن: الفوائد والمخاطر
من المهم أن تفصل في ذهنك بين فكرتي التزامن والتوازي، لأن لغة بايثون غير المتزامنة توفر الأولى ولكن ليس بالضرورة الثانية. التزامن يعني أن مهام متعددة تحرز تقدماً في فترات متداخلة، بينما يعني التوازي أنها تعمل حرفياً في نفس اللحظة على أنوية وحدة المعالجة المركزية المتعددة.
نموذج نموذجي للتعليمات البرمجية غير المتزامنة باستخدام asyncio لا يقوم بإنشاء خيوط نظام تشغيل متعددة؛ بل يقوم بتوزيع المهام في خيط واحد وفقًا لوقت تعطل كل منها بسبب عمليات الإدخال/الإخراج، على غرار برمجة غير متزامنة على Node.js. ولهذا السبب يتوسع بشكل جيد مع آلاف الاتصالات: تبديل السياق رخيص لأنه تعاوني ويتم التحكم فيه بواسطة حلقة الأحداث بدلاً من نظام التشغيل.
يأتي هذا التصميم مصحوباً بتحديات، خاصة فيما يتعلق بالتنسيق ومعالجة الاستثناءات. لأن منطقك الآن موزع على عدة إجراءات فرعية تتداخل مع مرور الوقت، يجب أن تكون أكثر دقة عند مشاركة الحالة، ونشر الأخطاء، وتنظيف الموارد. أخطاء مثل نسيان awaitقد تكون المهام التي لا يتم انتظارها أبدًا، أو الاستثناءات التي يتم ابتلاعها بصمت في مهام الخلفية، دقيقة ويصعب تصحيحها.
للحفاظ على قاعدة التعليمات البرمجية غير المتزامنة الخاصة بك قابلة للصيانة، يجب عليك اتباع ممارسات هندسية سليمة: حافظ على تركيز الروتينات الفرعية على مسؤولية واحدة، وقم بمركزة معالجة الأخطاء حيثما أمكن، وأضف تسجيلًا كافيًا لفهم ما يحدث في وقت التشغيل. تساهم الأدوات الجيدة والاتفاقيات الواضحة بشكل كبير في منع المشكلات الشبيهة بحالات التزامن أو تسرب الموارد، حتى في بيئة غير متزامنة أحادية الخيوط.
متى يكون استخدام الكود غير المتزامن مفيداً حقاً (ومتى لا يكون كذلك)
تُعد البرمجة غير المتزامنة فعالة بشكل لا يصدق لأحمال العمل المقيدة بالإدخال/الإخراج، لكنها ليست حلاً سحرياً لكل مشكلة في الأداء. تتمثل الخطوة الأولى في أي جهد لتحسين الأداء في تحديد ما إذا كانت الاختناقات ناتجة عن عمليات الإدخال/الإخراج أو عن العمليات الحسابية التي تعتمد على وحدة المعالجة المركزية.
إذا كان تطبيقك يقضي معظم وقته في انتظار استجابات الشبكة، وقراءة وكتابة الملفات، والاستعلام عن قواعد البيانات أو التواصل عبر المقابس، فإن البرمجة غير المتزامنة (async) هي بالتأكيد خيار مناسب. وتشمل الأمثلة النموذجية واجهات برمجة تطبيقات الويب التي تتصل بخدمات خارجية متعددة، وخطوط أنابيب ETL التي تقرأ وتكتب إلى مصادر بيانات متعددة في وقت واحد، والخدمات المصغرة التي تحافظ على العديد من اتصالات العملاء المتزامنة.
من ناحية أخرى، إذا كان عبء العمل لديك يهيمن عليه عمليات وحدة المعالجة المركزية الثقيلة مثل معالجة الأرقام أو معالجة الصور أو عمليات المحاكاة المعقدة، فإن عدم التزامن وحده لن يسرع الأمور. في مثل هذه الحالات، لا يزال قفل المفسر العام (GIL) يحد من العمليات التي يمكن تشغيلها بالتوازي ضمن عملية بايثون واحدة. عادةً ما ستحصل على نتائج أفضل باستخدام المعالجة المتعددة، أو الإضافات الأصلية، أو الاستفادة من واجهات برمجة التطبيقات المتخصصة.
في بيئات الشركات، تتمثل الاستراتيجية العملية في دمج هذه التقنيات: استخدام asyncio ومجموعات تطوير البرامج المدركة للبرمجة غير المتزامنة لخدمات السحابة (AWS وAzure وغيرها) لتقليل زمن الوصول وزيادة الإنتاجية إلى أقصى حد، مع تفويض العمل المكثف لوحدة المعالجة المركزية إلى عمليات منفصلة أو عمال أو خدمات حوسبة مُدارة. وبهذه الطريقة تستغل نقاط قوة كل أداة بدلاً من محاربة وقت تشغيل اللغة.
أفضل الممارسات لكتابة البرامج غير المتزامنة في بايثون
بمجرد أن تبدأ في تبني البرمجة غير المتزامنة على نطاق أوسع، ستساعدك أنماط وعادات معينة على تجنب أكثر المزالق شيوعًا. كما أنها تجعل الكود الخاص بك أكثر وضوحًا لزملائك في الفريق الذين قد لا يكونون على دراية عميقة بالنظام البيئي غير المتزامن حتى الآن.
تتمثل القاعدة الأساسية في تجنب استدعاءات الحظر في مسارات التعليمات البرمجية غير المتزامنة. هذا يعني استبدال أشياء مثل time.sleep() مع await asyncio.sleep()وتوخي الحذر عند استخدام المكتبات التي لا توفر واجهات برمجة تطبيقات متوافقة مع البرمجة غير المتزامنة. فإذا كانت حزمة خارجية متزامنة تمامًا، فإن استدعاءها بشكل متكرر من روتين فرعي قد يعيق حلقة الأحداث ويُفقدك مزايا التزامن.
عندما يكون لديك مجموعة من عمليات الإدخال/الإخراج المستقلة، يُفضل تشغيلها بشكل متزامن باستخدام أدوات مثل asyncio.gather() أو مجموعات من المهام مقيدة بمستوى أقصى للتزامن. يؤدي هذا النمط إلى زيادة الإنتاجية مع الحفاظ على التحكم في عدد الاتصالات المفتوحة أو الطلبات قيد التنفيذ.
كإرشادات تصميمية، حاول الحفاظ على الروتينات الفرعية صغيرة نسبيًا وتركز على مسؤولية واضحة، على غرار كيفية تصميم الوظائف في التعليمات البرمجية المتزامنة النظيفة. تصبح الروتينات الفرعية المتجانسة الكبيرة التي تمزج بين الشبكات ومنطق الأعمال ومعالجة الأخطاء صعبة الاختبار والتحليل بسرعة، خاصة عندما يفشل شيء ما في منتصف الطريق.
وأخيرًا، تحقق دائمًا مما إذا كانت مكونات النظام البيئي التي تعتمد عليها تدعم الاستخدام غير المتزامن بشكل حقيقي. توفر العديد من المكتبات الشائعة عملاءً منفصلين للبرمجة غير المتزامنة أو وحدات فرعية مخصصة؛ بينما قد تُبقي مكتبات أخرى على عملياتها غير متزامنة حتى وإن كانت تُعلن عن ميزات "البرمجة غير المتزامنة". لذا، فإن قراءة الوثائق بعناية وإجراء اختبارات أداء بسيطة يُمكن أن يُجنّبك التراجع الطفيف في الأداء.
سيناريوهات الاستخدام العملي وأفكار التصميم المعماري
في مشاريع البرمجيات الواقعية، يتألق البرمجة غير المتزامنة في مجموعة متنوعة من البنى، بدءًا من الواجهات الخلفية التقليدية للويب وحتى الأنظمة المتطورة التي تعمل بالذكاء الاصطناعي. العنصر الموحد هو دائماً الحاجة إلى التعامل مع العديد من العمليات المرتبطة بالإدخال/الإخراج دون إضاعة الوقت في الانتظار الخامل.
أحد السيناريوهات الكلاسيكية هو خدمة الويب التي تحتاج إلى استدعاء العديد من واجهات برمجة التطبيقات الخارجية لإنشاء استجابة واحدة للعميل. باستخدام البرمجة غير المتزامنة، يمكن للخدمة إرسال جميع الطلبات الصادرة دفعة واحدة وتجميع البيانات النهائية فور وصول كل جزء، مما يقلل وقت الاستجابة الإجمالي بشكل ملحوظ. وهذا شائع في بنى الخدمات المصغرة وعمليات التكامل مع بوابات الدفع، وشبكات التواصل الاجتماعي، ومنصات التحليلات.
ومن حالات الاستخدام المهمة الأخرى هندسة البيانات: حيث تتفاعل خطوط الأنابيب ووظائف ETL بشكل متكرر مع قواعد بيانات متعددة أو أنظمة ملفات أو حاويات تخزين سحابية بالتوازي. من خلال القراءة من مصادر متعددة في وقت واحد وكتابة النتائج بمجرد أن تصبح جاهزة، يمكنك تقليل زمن الوصول الإجمالي والاستفادة بشكل أفضل من النطاق الترددي المتاح، خاصة عند العمل مع التخزين السحابي أو واجهات برمجة تطبيقات البيانات القائمة على REST.
كما أن البرمجة غير المتزامنة تتكامل بشكل جيد مع لوحات معلومات ذكاء الأعمال وأدوات مثل Power BI، حيث يجب على الأنظمة الخلفية تجميع البيانات من خدمات مختلفة دون حظر اتصالات HTTP طويلة الأمد. بناء طبقات واجهة برمجة التطبيقات المخصصة أو خدمات التكامل المصغرة باستخدام asyncio يمكن أن يحسن الاستجابة المتصورة والإنتاجية تحت الضغط.
غالباً ما تعتمد الشركات المتخصصة في البرمجيات المخصصة والذكاء الاصطناعي والأمن السيبراني والاستشارات السحابية بشكل كبير على التقنيات غير المتزامنة لتنسيق سير العمل الذي يستدعي نماذج الذكاء الاصطناعي، ويسجل الأحداث، ويراقب التهديدات، ويتواصل مع منصات التحكم السحابية. إن الجمع بين عمليات الإدخال/الإخراج غير المتزامنة للتنسيق مع عمال وحدة المعالجة المركزية المنفصلين للقيام بالمهام الثقيلة هو نمط داخلي شائع ينتج عنه أنظمة قابلة للتوسع والصيانة.
بالنسبة للعديد من المطورين والفرق، تتمثل الخطوة الأولى ببساطة في إدخال البرمجة غير المتزامنة في أجزاء التطبيق التي تصرخ بوضوح "مقيدة بالإدخال/الإخراج"، ثم التكرار من هناك عندما تصبح الفوائد واضحة ويكتسب الفريق الثقة في النماذج والأدوات.
في النهاية، تدور البرمجة غير المتزامنة في بايثون حول استخدام وقت الانتظار بحكمة: من خلال هيكلة التعليمات البرمجية الخاصة بك حول async, awaitباستخدام الروتينات الفرعية وحلقة الأحداث، يمكنك بناء تطبيقات تبدو أسرع، وتتوسع بشكل أفضل تحت الضغط، وتستفيد إلى أقصى حد من الموارد المتاحة، خاصة عند التعامل مع الشبكات والملفات والخدمات الخارجية.
