نموذج الخيوط في JavaFX
نموذج الخيوط في JavaFX
تفرض كل إطار عمل للواجهات الرسومية قاعدة واحدة: يجب أن تتم جميع التعديلات على الشجرة المشهدية (scene graph) في خيط واحد مخصص. في JavaFX يُسمّى هذا الخيط خيط تطبيق JavaFX (JAT). إن فهم سبب وجود هذه القاعدة، وكيف يفرضها وقت التشغيل، وكيف تعمل ضمنها ليس معلومة اختيارية — بل هو الفرق بين تطبيق يعمل بشكل موثوق وآخر يتعطل أو يتجمد بصورة غير متوقعة في بيئة الإنتاج.
لماذا خيط واحد للواجهة الرسومية؟
الشجرة المشهدية هي شجرة من كائنات Node. عندما تجتاز خط التصيير تلك الشجرة لإنتاج إطار، يجب أن يرى لقطة متسقة. لو عدّل خيطان في وقت واحد خصائص عقد مختلفة، فقد يصادف المصيّر تغييرات نُفِّذت جزئيًا: زر في منتصف إعادة تحجيمه بينما يُستبدَل تسميته. سيكون الفساد البصري الناتج، أو ما هو أسوأ من ذلك — ConcurrentModificationException — بالغ الصعوبة في إعادة إنتاجه وتصحيحه.
بدلًا من إجبار كل وصول للعقد على المرور بقفل (lock) — الذي سيُتسلسل واجهة المستخدم بالكامل ويجعل أخطاء الخيوط صعبة الاكتشاف في الوقت المناسب — يتبع JavaFX النهج الأبسط والمُثبَت: تعيين خيط واحد يملك الشجرة المشهدية، وجعل الإطار يكتشف الكتابات من أي خيط آخر ويرفضها في وقت التشغيل.
Application.launch()، يبدأ JavaFX خيط JAT ويستدعي start(Stage) عليه. كل ما تفعله داخل start()، وجميع معالجات الأحداث المربوطة بعناصر التحكم، تعمل تلقائيًا على JAT. لن تحتاج للتفكير في الخيوط إلا عند إدخال عمل في الخلفية.
ما يحدث عند انتهاك القاعدة
إذا حاولت تعديل عقدة في شجرة المشهد الحية من خيط خلفية، يرمي JavaFX استثناء IllegalStateException برسالة "Not on FX application thread". في إصدارات وقت التشغيل القديمة قد يُفسد الانتهاك الحالة بصمت بدلًا من الرمي — وهذا أسوأ بكثير. في كلتا الحالتين الإصلاح واحد: أعد نقل التحديث إلى JAT.
Platform.runLater — الجسر بين الخيوط
Platform.runLater(Runnable) هي الآلية الأساسية لإعادة نشر العمل على JAT. تستدعيها من أي خيط وتمرر Runnable؛ يضع وقت تشغيل JavaFX التشغيلي في قائمة الانتظار وينفّذه على JAT في دورة الإطار (النبضة) التالية. الاستدعاء غير مُعيق: يستمر خيط الخلفية فورًا بعد أن تعود runLater().
لاحظ الفصل بين المسؤوليات: تحتوي دالة lambda للـ Thread على الحساب فقط، ويحتوي كلٌّ من كتلتَي runLater على تحديثات واجهة المستخدم فقط. هذا النمط هو العمود الفقري لكل تطبيق JavaFX متجاوب.
Platform.runLater مقابل Platform.runAndWait
يوفر JavaFX أيضًا Platform.runAndWait(Runnable). على خلاف runLater، فهو يعلّق الخيط المُستدعي حتى ينتهي التشغيلي من التنفيذ على JAT. نادرًا ما يكون هذا هو الاختيار الصحيح:
- runLater: أطلق وانسَ؛ يستمر خيط الخلفية فورًا. استخدمه في كل الحالات تقريبًا.
- runAndWait: يعلّق خيط الخلفية حتى تتم المعالجة. استخدمه فقط حين تحتاج فعلًا قيمة إرجاع محسوبة على JAT قبل المتابعة، ومن خيط غير JAT فقط. استدعاء
runAndWaitمن JAT نفسه يرمي استثناءً.
Platform.runLater آلاف المرات في الثانية (مثلًا من حلقة محاكاة مشددة)، تمتلئ قائمة أحداث JAT أسرع مما تستطيع الإفراغ. ستتجمد واجهة المستخدم. الحل هو تقليل التحديثات: اجمع النتائج في متغير مشترك وانشر تحديثًا واحدًا لكل إطار رسوم متحركة، أو استخدم javafx.concurrent.Task الذي يتعامل مع هذا تلقائيًا.
التحقق مما إذا كنت بالفعل على JAT
أحيانًا يمكن استدعاء دالة بشكل مشروع إما من JAT أو من خيط خلفية. استخدم Platform.isFxApplicationThread() للتفرع بأمان:
javafx.concurrent.Task — التجريد رفيع المستوى
لمعظم المهام الخلفية الحقيقية، Task<V> (في حزمة javafx.concurrent) أنظف من الخيوط الخام مع نداءات runLater يدوية. تلفّ Task عقد الخيوط لك: تتجاوز call() لعمل الخلفية، وتربط عناصر واجهة المستخدم بخصائصها القابلة للملاحظة (messageProperty، progressProperty، valueProperty). يضمن الإطار تحديث الخصائص على JAT.
Task على الخيوط الخام للعمل الخلفي. يمنحك دعمًا للإلغاء (task.cancel())، ومعالجة الاستثناءات (setOnFailed)، وتتبع التقدم، وتوجيه JAT الصحيح — كل هذا دون نداء واحد يدوي لـ runLater.
نظام النبضات: متى تتحدث واجهة المستخدم فعلًا؟
لا يُعيد JavaFX تصيير المشهد بعد كل تغيير في الخاصية. بدلًا من ذلك يُدمج التغييرات ويُعيد الرسم على نبضة منتظمة — عادةً بمعدل 60 هيرتز، متزامنة مع معدل تحديث الشاشة عبر ردود نداء AnimationTimer. عند استدعاء runLater، يعمل تشغيليك خلال مرحلة معالجة أحداث النبضة التالية، وتنعكس التغييرات التي تجريها في مرحلة التصيير لنفس الإطار. لهذا السبب تبدو نداءات runLater المتعددة المنشورة بسرعة من خيط خلفية أنها "تُدمَج" بصريًا — فقد تُنفَّذ جميعها في نبضة واحدة.
الخلاصة
يُبنى نموذج خيوط JavaFX على قاعدة واحدة: الشجرة المشهدية تُلمَس فقط على JAT. Platform.runLater(Runnable) هي صمام الأمان — يُدرج تحديث واجهة مستخدم من أي خيط. للعمل الخلفي غير التافه، تُطبّق Task<V> فوق هذه الأداة البدائية وتمنحك تقدمًا قابلًا للملاحظة وإلغاءً ومعالجة أخطاء. احترام هذه الحدود هو ما يُبقي تطبيق JavaFX سريعًا وصحيحًا وخاليًا من الأعطال. في الدرس الأخير التالي ستطبّق كل ما تعلمته من هذا البرنامج التعليمي ببناء تطبيق JavaFX صغير كامل.