يمكن لمهمة asyncio الخاصة بك أن تتوقف في منتصف العمل
تستخدم العديد من خدمات Python هذا النمط لتشغيل العمل في الخلفية:
async def create_order(order):
await save(order)
asyncio.create_task(send_webhook(order))
return {"status": "created"}
الهدف هو إرسال webhook دون جعل العميل ينتظر. هذا يعمل في بيئة التطوير، ولكن في بيئة الإنتاج، يفشل الأمر بشكل عشوائي.
المشكلة تكمن في عملية جمع المهملات (garbage collection).
عندما تستدعي asyncio.create_task ولا تقوم بحفظ النتيجة، تصبح المهمة مؤهلة لعملية جمع المهملات. يحتفظ الـ event loop فقط بمراجع ضعيفة (weak references) للمهام. إذا لم يكن هناك شيء آخر يشير إلى تلك المهمة، فقد يقوم Python باستعادتها بينما لا تزال قيد التشغيل.
يتوقف عملك ببساطة. لا توجد أخطاء في نظام تتبع الأخطاء لديك. الكود مثالي، لكن المهمة تختفي ببساطة.
قد ترى هذا السجل في stderr الخاص بك:
Task was destroyed but it is pending!
يصعب العثور على هذا الخطأ لأنه غالبًا ما يظهر بعد فترة طويلة من الفشل الفعلي.
كيفية الإصلاح:
استخدم مجموعة مهام خلفية (background task set) للاحتفاظ بالمراجع.
background_tasks = set()
def fire_and_forget(coro):
task = asyncio.create_task(coro)
background_tasks.add(task)
task.add_done_callback(background_tasks.discard)
return task
تحافظ المجموعة على بقاء المهمة حية. وتقوم دالة الاستدعاء (callback) بإزالتها بمجرد انتهاء العمل. هذا يمنع تسرب الذاكرة (memory leaks) ويمنع جامع المهملات من إنهاء مهامك.
بالنسبة لـ Python 3.11 والإصدارات الأحدث، يمكنك استخدام TaskGroups. تحتفظ TaskGroup بمراجع قوية (strong references) لكل مهمة بداخلها. لاحظ أن TaskGroup ستنتظر انتهاء جميع المهام قبل الخروج من الكتلة (block). استخدم هذا إذا كان يجب إكمال العمل قبل الرد على المستخدم.
للأعمال الثقيلة في الخلفية، استخدم نمط المشرف (supervisor pattern):
- أنشئ مهمة واحدة طويلة الأمد عند بدء التشغيل.
- استخدم طابوراً (queue) لإرسال العمل إليها.
- يقوم معالج الطلبات (request handler) فقط بإضافة العناصر إلى الطابور.
يضمن ذلك بقاء عملك ويسمح لك بإضافة عمليات إعادة المحاولة لاحقاً.
في Python، يجب أن تمتلك مهامك. إذا لم تحتفظ بمرجع لها، فلن يقوم وقت التشغيل (runtime) بحمايتها.
المصدر: https://dev.to/r9v/your-asyncio-task-can-be-garbage-collected-mid-flight-3kg1
