حلقة الأحداث
في مجال البرمجيات ، تُعدّ حلقة الأحداث خوارزمية تُرسل باستمرار تدفق التحكم للأحداث . تطلب الحلقة الحدث التالي من مُزوّد الأحداث (الذي عادةً ما يُوقف الحلقة حتى وقوع الحدث)، وعند استلام الحدث، تستدعي مُعالج الأحداث المرتبط به . عندما تكون حلقة الأحداث هي مُرسل الأحداث المركزي للبرنامج ، كما هو الحال غالبًا، تُسمى الحلقة الرئيسية أو حلقة الأحداث الرئيسية .
في البيئات الحديثة مثل متصفحات الويب وبيئات تشغيل الخوادم، تُعدّ حلقة الأحداث آلية أساسية تُمكّن من التنفيذ غير المتزامن من خلال المراقبة المستمرة للأحداث أو الرسائل من قائمة انتظار وإرسالها عندما يكون خيط البرنامج الرئيسي في وضع الخمول. في جافا سكريبت، تسمح حلقة الأحداث بمعالجة المهام دون حظر، مثل تفاعلات المستخدم والمؤقتات وعمليات الإدخال/الإخراج، على الرغم من أن اللغة أحادية الخيوط. [ 1 ]
يمكن استخدام الخوارزمية نفسها لمعالجة الرسائل الواردة ، وهي مجموعة فرعية من الأحداث. في هذا السياق، تُسمى الخوارزمية حلقة الرسائل ، أو موزع الرسائل ، أو مضخة الرسائل . من الاستخدامات الشائعة لحلقة الرسائل تبادل الرسائل بين العمليات، حيث تتم إدارة قائمة انتظار الرسائل خارج البرنامج (مثلًا بواسطة نظام التشغيل ).
عادةً، يستخدم البرنامج الذي يعمل في بيئة واجهة المستخدم الرسومية (GUI) حلقة أحداث، ونظرًا لانتشار بيئات واجهة المستخدم الرسومية، فإن معظم التطبيقات الحديثة تحتوي على حلقة أحداث رئيسية.
في الشفرة الزائفة التالية، get_next_message()يمثل عنصرًا نائبًا لدالة يوفرها نظام التشغيل عادةً، والتي تتوقف مؤقتًا حتى تتوفر رسالة. وبالتالي، لا تتكرر الحلقة إلا عند وجود رسالة للمعالجة.
حلقة message := get_next_message() process_message(message) بينما الرسالة لا تساوي "خروج"
البدائل
تتضمن العديد من البرامج حلقة أحداث في التصميم عالي المستوى؛ حلقة أحداث رئيسية، ولكن هناك تصميمات بديلة.
يمكن للبرنامج أن يتوقف بمجرد إتمام المهام الموكلة إليه دون تدخل خارجي (أي من المستخدم ). غالبًا ما يتضمن هذا النوع من البرامج سلوكًا اختياريًا يتم ضبطه قبل بدء تشغيله، مثلاً عبر واجهة سطر الأوامر أو متغيرات البيئة . وتُعدّ برامج الأدوات المساعدة من هذا النوع في كثير من الأحيان.
حتى لو كان البرنامج يسمح بتفاعل المستخدم، فقد لا يستخدم حلقة أحداث. أما البرنامج الذي يوفر واجهة سطر أوامر داخلية، فيتضمن معالج أوامر يشبه حلقة الأحداث في كونه يُرسل الأوامر إلى معالجات الأوامر بناءً على مدخلات المستخدم.
أمثلة
HTML/JavaScript
عادةً ما تعمل صفحة الويب وبرنامج جافا سكريبت الخاص بها ضمن عملية متصفح ويب أحادية المسار . تتعامل عملية المتصفح مع الرسائل الواردة من قائمة الانتظار واحدة تلو الأخرى. قد ترتبط دالة جافا سكريبت أو حدث آخر في المتصفح برسالة معينة. عندما تنتهي عملية المتصفح من معالجة رسالة، تنتقل إلى الرسالة التالية في قائمة الانتظار.
في بيئات جافا سكريبت، تعمل حلقة الأحداث مع قوائم انتظار منفصلة - بما في ذلك قائمة انتظار المهام وقائمة انتظار المهام الصغيرة - لإدارة العمليات غير المتزامنة مثل المؤقتات، واستدعاءات الوعود، وأحداث DOM. عندما يكون مكدس الاستدعاءات فارغًا، تختار حلقة الأحداث المهمة التالية من قوائم الانتظار هذه لتنفيذها. [ 2 ]
تطبيقات ويندوز
في نظام التشغيل ويندوز ، يجب على أي عملية تتفاعل مع المستخدم قبول الرسائل الواردة والاستجابة لها، ويتم ذلك غالبًا عبر حلقة رسائل داخل تلك العملية. في ويندوز، تُعتبر الرسالة بمثابة حدث يتم إنشاؤه وفرضه على نظام التشغيل. يمكن أن يكون الحدث تفاعل المستخدم، أو حركة مرور الشبكة، أو معالجة النظام، أو نشاط المؤقت، أو الاتصال بين العمليات، وغيرها. بالنسبة للأحداث غير التفاعلية، التي تقتصر على عمليات الإدخال/الإخراج، يوفر ويندوز منافذ إكمال الإدخال/الإخراج . تعمل حلقات منافذ إكمال الإدخال/الإخراج بشكل منفصل عن حلقة الرسائل، ولا تتفاعل معها افتراضيًا.
تُعدّ الدالة WinMain() جوهر معظم تطبيقات Win32 ، حيث تستدعي الدالة GetMessage() ضمن حلقة تكرارية. تتوقف الدالة GetMessage() مؤقتًا حتى يتم استقبال رسالة (حدث) (مع وجود الدالة PeekMessage() كبديل غير متوقف مؤقتًا). بعد معالجة اختيارية، تستدعي الدالة DispatchMessage() ، التي بدورها تُرسل الرسالة إلى المعالج المناسب، المعروف أيضًا باسم WindowProc . عادةً، تُرسل الرسائل التي لا تحتوي على معالج WindowProc() خاص إلى DefWindowProc ، وهو المعالج الافتراضي. تستدعي الدالة DispatchMessage() معالج WindowProc الخاص بمعرّف HWND للرسالة (المسجل باستخدام الدالة RegisterClass() ).
تضمن الإصدارات الأحدث من نظام التشغيل ويندوز وصول الرسائل إلى حلقة الرسائل الخاصة بالتطبيق بالترتيب الذي استقبلها به النظام وأجهزته الطرفية. يُعد هذا الضمان أساسيًا عند النظر في تبعات تصميم التطبيقات متعددة الخيوط . مع ذلك، تخضع بعض الرسائل لقواعد مختلفة، مثل الرسائل التي تُستقبل دائمًا في النهاية، أو الرسائل ذات الأولوية الموثقة المختلفة. [ 3 ]
حلقة أحداث Xlib
تُبنى تطبيقات X التي تستخدم XlibXNextEvent مباشرةً حول مجموعة من الدوال؛ XNextEventحيث تُوقف التنفيذ مؤقتًا حتى يظهر حدث في قائمة انتظار الأحداث، وعندها يُعالج التطبيق الحدث بشكل مناسب. تتعامل حلقة أحداث Xlib فقط مع أحداث نظام النوافذ؛ أما التطبيقات التي تحتاج إلى انتظار ملفات وأجهزة أخرى، فيمكنها إنشاء حلقة أحداث خاصة بها باستخدام أدوات بدائية مثل `xlib` ConnectionNumber، ولكنها في الواقع العملي تميل إلى استخدام تعدد الخيوط .
تستخدم برامج قليلة جدًا مكتبة Xlib بشكل مباشر. في الحالة الأكثر شيوعًا، تدعم مجموعات أدوات واجهة المستخدم الرسومية المبنية على Xlib عادةً إضافة الأحداث. على سبيل المثال، تحتوي مجموعات الأدوات المبنية على Xt IntrinsicsXtAppAddInput() على وظيفتي و XtAppAddTimeout().
لا يُنصح باستدعاء دوال Xlib من معالج الإشارات، لأن تطبيق X قد يكون قد توقف في حالة عشوائية، على سبيل المثال داخل XNextEvent. انظرلإيجاد حل لـ X11R5 و X11R6 و Xt.
حلقة أحداث GLib
تم تصميم حلقة أحداث GLib في الأصل للاستخدام في GTK ، ولكنها تُستخدم الآن في تطبيقات غير رسومية أيضًا، مثل D-Bus . المورد الذي يتم استطلاعه هو مجموعة مُعرّفات الملفات التي يهتم بها التطبيق؛ وسيتم مقاطعة كتلة الاستطلاع عند وصول إشارة أو انتهاء مهلة زمنية (على سبيل المثال، إذا حدد التطبيق مهلة زمنية أو مهمة خاملة). على الرغم من أن GLib يدعم بشكل مُدمج أحداث مُعرّفات الملفات وإنهاء العمليات الفرعية، فمن الممكن إضافة مصدر حدث لأي حدث يمكن معالجته في نموذج التحضير والتحقق والإرسال.
تتضمن مكتبات التطبيقات المبنية على حلقة أحداث GLib كلاً من GStreamer وطرق الإدخال/الإخراج غير المتزامنة في GnomeVFS ، إلا أن GTK تبقى المكتبة الأكثر شيوعًا بين مكتبات العميل. يقوم GDK بترجمة الأحداث الواردة من نظام النوافذ (في X ، تُقرأ من مقبس X) إلى أحداث GTK، ثم تُرسل كإشارات GLib إلى عناصر واجهة المستخدم الخاصة بالتطبيق.
حلقات تشغيل macOS Core Foundation
يُسمح بحلقة تشغيل واحدة فقط لكل خيط، ويمكن ربط عدد غير محدود من المصادر والمراقبين بها. تتواصل المصادر مع المراقبين عبر حلقة التشغيل، التي بدورها تنظم عملية ترتيب الرسائل وإرسالها.
يتم تجريد CFRunLoop في Cocoa على أنه NSRunLoop، مما يسمح بوضع أي رسالة (مكافئة لاستدعاء دالة في أوقات التشغيل غير الانعكاسية ) في قائمة الانتظار لإرسالها إلى أي كائن.
قائم على الملفات
في نظام يونكس ، يؤدي نموذج " كل شيء عبارة عن ملف" إلى حلقة أحداث قائمة على الملفات. تتم عمليات القراءة والكتابة من وإلى الملفات، والتواصل بين العمليات، والتواصل عبر الشبكة، والتحكم في الأجهزة، باستخدام عمليات الإدخال والإخراج للملفات، حيث يتم تحديد الهدف بواسطة مُعرِّف الملف . تسمح استدعاءات النظام select و poll بمراقبة مجموعة من مُعرِّفات الملفات لرصد أي تغيير في الحالة، مثلاً عند توفر البيانات للقراءة.
على سبيل المثال، لنفترض برنامجًا يقرأ من ملف يتم تحديثه باستمرار ويعرض محتوياته في نظام X Window ، والذي يتواصل مع العملاء عبر مقبس (إما نطاق Unix أو Berkeley ):
def main (): file_fd = open ( "logfile.log" ) x_fd = open_display () construct_interface () while True : rlist , _ , _ = select.select ([ file_fd , x_fd ], [ ] , []): if file_fd in rlist : data = file_fd.read ( ) append_to_display ( data ) send_repaint_message () if x_fd in rlist : process_x_messages ( )يعتمد على الإشارة
في نظام يونكس، الإشارة هي حدث غير متزامن تتم معالجته بواسطة معالج إشارات يعمل أثناء تعليق باقي المهمة. إذا تم استقبال إشارة ومعالجتها أثناء حظر المهمة select()، فإن الأمر select سيعود مبكرًا مع رمز الخطأ EINTR ؛ أما إذا تم استقبال إشارة أثناء استهلاك المهمة لوحدة المعالجة المركزية ، فسيتم تعليق المهمة بين التعليمات حتى يعود معالج الإشارات.
إحدى طرق معالجة الإشارات هي أن تقوم معالجات الإشارات بتعيين علامة عامة، ثم يقوم حلقة الأحداث بالتحقق من هذه العلامة مباشرةً قبل الاستدعاء وبعده select()؛ فإذا كانت مُعيّنة، تُعالج الإشارة بنفس طريقة معالجة أحداث مُعرّفات الملفات. لسوء الحظ، يُؤدي هذا إلى حالة تنافس : فإذا وصلت إشارة مباشرةً بين التحقق من العلامة واستدعاء الدالة select()، فلن تُعالج حتى select()تعود الدالة لسبب آخر (على سبيل المثال، مقاطعة من قِبل مستخدم مُحبط).
الحل الذي توصلت إليه POSIX هو pselect()استدعاء مشابه لـ `require` select()ولكنه يأخذ مُعاملًا إضافيًا sigmaskيصف قناع الإشارة . يسمح هذا للتطبيق بإخفاء الإشارات في المهمة الرئيسية، ثم إزالة القناع طوال مدة الاستدعاء select()بحيث لا يتم استدعاء معالجات الإشارات إلا عندما يكون التطبيق مُقيدًا بعمليات الإدخال /الإخراج . مع ذلك، لم تكن تطبيقات `require` pselect()موثوقة دائمًا؛ فإصدارات لينكس السابقة للإصدار 2.6.16 لا تحتوي على pselect()استدعاء نظام `require`، [ 4 ] مما يُجبر مكتبة glibc على محاكاته عبر طريقة عُرضة لنفس حالة التزامن pselect()التي يُراد تجنبها.
يتمثل الحل البديل والأكثر قابلية للنقل في تحويل الأحداث غير المتزامنة إلى أحداث قائمة على الملفات باستخدام تقنية الأنبوب الذاتي [ 5 ] ، حيث يقوم معالج الإشارة بكتابة بايت إلى أنبوب تتم مراقبة طرفه الآخر select()في البرنامج الرئيسي [ 6 ] . في إصدار نواة لينكس 2.6.22، تمت إضافة استدعاء نظام جديد signalfd()يسمح باستقبال الإشارات عبر واصف ملف خاص.
جافا سكريبت والتنفيذ غير المتزامن
في بيئات مثل متصفحات الويب وNode.js، تُعدّ حلقة الأحداث أساسية لسلوك البرامج غير المتزامنة. يعمل جافا سكريبت على خيط واحد للتنفيذ، لكن بيئات الاستضافة توفر واجهات برمجة تطبيقات (APIs) للعمليات غير الحظرية، مثل المؤقتات وطلبات الشبكة وأحداث المستخدم. تُوضع العمليات غير المتزامنة المكتملة في قوائم انتظار، وتتحقق حلقة الأحداث بشكل متكرر مما إذا كان مكدس الاستدعاءات فارغًا لجدولة عمليات الاستدعاء المُدرجة في قائمة الانتظار. يُمكّن هذا تطبيقات جافا سكريبت من الحفاظ على استجابتها أثناء معالجة مهام غير متزامنة متعددة. [ 7 ]
انظر أيضاً
مراجع
- ↑ "نموذج تنفيذ جافا سكريبت - جافا سكريبت | MDN" . وثائق MDN على الويب . 2025-11-07 . تم الاطلاع عليه بتاريخ 2026-01-03 .
- ↑ "نموذج تنفيذ جافا سكريبت - جافا سكريبت | MDN" . وثائق MDN على الويب . 2025-11-07 . تم الاطلاع عليه بتاريخ 2026-01-03 .
- ↑ دالة GetMessage() مع قائمة أولويات الرسائل.
- ↑ "Linux_2_6_16 - Linux Kernel Newbies" . kernelnewbies.org . تم الاطلاع عليه بتاريخ 2021-03-03 .
- ↑ دي جي بيرنشتاين. "خدعة الأنبوب الذاتي" .
- ↑ الأخطاء : تعدد إرسال/إدخال متزامن - دليل مبرمج لينكس - استدعاءات النظام من Manned.org
- ↑ "نموذج تنفيذ جافا سكريبت - جافا سكريبت | MDN" . وثائق MDN على الويب . 2025-11-07 . تم الاطلاع عليه بتاريخ 2026-01-03 .
روابط خارجية
- تدفق التحكم
- الأحداث (الحوسبة)
