السلام عليكم....
مقدمه
ما دفعني لكتابه الموضوع هو اثاره موضوع ال EDT في درس للأخ محمود حيث قام الاستاذ عصام مشكورا بتنبيهه الى ضروره تحديث ال Swing UI من داخل ال EDT. طبعا ذهبت وبحثت عن الموضوع فلم أجد مقالات تشرح الموضوع بشكل مفصل لذلك قررت الكتابه عنه نظرا لأهميه الموضوع.
في البدايه لقد تعاملت مع ثلاث مكتبات مختلفه تعتمد بشكل أساسي على وجود ال EDT اثنتان منها للهواتف النقاله وهما LWUIT و BlackBerry UI وأخرى للحواسيب الشخصيه وهي Swing لذلك فان ال EDT موجوده في مكتبات عديده وحتى أن بعضها يوجد في مكتبات ليست مكتوبه بلغه جافا ولكن جميعها تشترك في الأساسيات.
عند عمل اي برنامج يعتمد على مكتبه GUI تعتمد في داخلها على وجود EDT فاعلم أنه لحظه ظهور ال GUI أصبح عندك خيطان الأول هو ال Main Thread وهو ما نعلمه جميعا والثاني هو ال EDT وهو ما نتحدث عنه اليوم. هذا الخيط الثاني هو المسؤول عن جميع عمليات رسم المكونات الرسوميه واستقبال الأحداث وتوزيعها على المكونات التي يجب عليها استقبال هذه الأحداث.
أين المشكله
تظهر أكثر المشاكل عند المبرمجين أثناء انشائهم خيوطا جديده في برنامجهم لعمل بعض العمليات وعندها يقومون خطأ بتعديل مكونات ال Swing من داخل هذه الخيوط الجديده وهو ما يشكل كارثه حقيقيه على البرنامج. مشكله هذا النوع من الأخطاء أنه صعب التتبع فعند وقوعه سوف يصدر الخطأ من داخل مكتبه ال Swing وسوف يكون خطأ غريبا مثلا يمكن أن ينطلق خطأ ArrayIndexOutOfBoundsException وعندها ستبقى أياما طوالا تحك رأسك كيف يخرج هذا الخطأ من داخل مكتبه ال Swing. وتفكر كيف ظهر خطأ مثل هذا وربما الكود الذي كتبته لا يحتوي على Array من الأساس.
امكانيه حدوث الخطأ
على الحاسوب الشخصي الامكانيه ضعيفه جدا حيث أني حاولت متعمدا عطب ال EDT في Swing ولكني لم أنجح ربما لأن جهازي Quad Core لا أعلم ولكن تبقي الامكانيه موجوده ( من يستطيع اعطاب ال EDT بمثال, أرجو أن يضعه ), أما على الهواتف النقاله فأنا أضمن لك أنك اذا لم تحدث ال GUI من داخل ال EDT فان برنامجك سيتعطل بنسبه 80% وربما على الهواتف الضعيفه تصل النسبه ال 90%.
القاعده الذهبيه
قم بتحديث ال GUI من داخل ال EDT فقط
تشريح ال EDT
أنا شخصيا أفضل الكود على الكلام المطول، أراه أسهل للفهم. لذلك سأقوم بوضع كود تخيلي لل EDT حتى نوضح المشكله, طبعا الكود يوضح عمل ال EDT لكنه مبسط بشكل كبير لتوضيح طريقه عمله أما في الواقع فهو أعقد من هذا.
import java.awt.Graphics;
import java.util.Vector;
import javax.swing.JFrame;
public class EDT extends Thread {
private boolean quit;
private Vector tasks;
private Graphics g;
private long frameRate = 50;
private JFrame currentFrame;
@Override
public void run() {
while(!quit) {
long currentTime = System.currentTimeMillis();
handleEvents();
currentFrame.paint(g);
processTasks();
long timeTaken = System.currentTimeMillis() - currentTime;
try {
Thread.sleep( frameRate - timeTaken );
} catch(Exception ex ) {
}
}
}
private void handleEvents() {
// handle events
}
private void processTasks() {
for( int i=0;i<tasks.size();i++ ) {
Runnable task = (Runnable)tasks.elementAt( i );
task.run();
tasks.removeElementAt( i );
}
}
public void invokeLater( Runnable task ) {
tasks.addElement( task );
}
}كما هو واضح فان ال EDT هو عباره عن حلقه تكراريه غير منتهيه حتى ينتهي التطبيق. تنقسم الحلقه التكراريه الى ثلاث أقسام رئيسه:
1. الجزء المسؤول عن معالجه الأحداث وارسالها الى المكونات المطلوبه handleEvents.
2. الجزء المسؤول عن رسم المكونات.
3. الجزء السؤول عن معالجه المهام التي يجب ان تنفذ داخل ال EDT وتمت اضافتها من خيوط أخرى ( غالبا ).
توضيح المشكله
الأن تعالوا نتخيل معا وجود JList داخل ال JFrame ووجود خيط ثاني Thread B ينتظر وصول معلومات من الشبكه لتحديث هذه القائمه.
لنفترض ان ال EDT كان يقوم بعمل رسم لمكونات ال JFrame والتي يمكن تصور الكود التابع لها بهذه الطريقه:
public void paint(Graphics g) {
for( int i=0;i<components.size();i++ ) {
JComponent component = (JComponent)components.elementAt( i );
component.paint( g );
}
}الان أحد هذه ال components هو ال JList كما ذكرنا سابقا، ولنفترض ان ال EDT وصل في التنفيذ الى رسم ال JList والتي يمكن تخيل كود رسمها كالتالي:
public void paint(Graphics g ) {
for( int i=0;i<getModel().getSize();i++ ) {
Object ob = getModel().getElementAt( i );
// draw ob
}
}الان تخيل معي ان النظام قرر تبديل الخيط و تشغيل ال Thread B وتوقف ال EDT عند الحلقه 30 من أصل 50 حلقه تساوي عدد العتاصر الموجوده في ال JList. في ال Thread B كان ينفذ الكود التالي داخل الخيط وليس داخل ال EDT:
list.removeAll();
String[] options = new String[10];
for (int i = 0; i < 10; i++) {
options = "New Option " + (i + 1);
}
list.setListData( options );بعد تنفيذ هذا الكود عاد ال EDT للعمل وقد كان توقف عند الحلقه 30 سوف يدخل الى الحلقه 31 وعند أول سطر سوف ينطلق خطأ ArrayIndexOutOfBoundsException نتيجه لأن ال list أصبح بها الان 10 عناصر فقط وليس 50.
الحل
تنفيذ الكود السابق داخل ال EDT عن طريق invokeLater كالتالي:
invokeLater( new Runnable() {
public void run() {
list.removeAll();
String[] options = new String[10];
for (int i = 0; i < 10; i++) {
options = "New Option " + (i + 1);
}
list.setListData( options );
}
});بالتالي عند تنفيذ ال Thread B سوف نقوم بانشاء كائن من ال Runnable، لاحظ أن هذا ليس خيطا جديدا وهو يحتوي على ما يجب تنفيذه داخل ال EDT والان لنعد لمثالنا نحن توقفنا عند الحلقه 30 من ال EDT ثم انتقلنا ال Thread B الذي بدوره أضاف كائن ال Runnable الى ال tasks التي يجب تنفيذها داخل ال EDT ثم نعود الى EDT الذي يكمل حلقاته التكراريه الى الحلقه 50 دون حدوث خطـأ لأننا لم ننفذ كود تحديث ال list. الان في الحلقه الثانيه من ال while loop الرئيسه في ال EDT سوف يقوم بمعالجه الأحداث والرسم ومن ثم معالجه المهام المضافه من الخيوط الأخرى وعندها سوف يتم تحديث ال list دون مشاكل وفي الحلقه التي تليها سوف يرسم ال list الجديده.
الخلاصه
كل الكلام السابق اختصاره، قم بتحديث ال GUI داخل ال EDT :)
ملاحظه
أنا لست محترف في ال Swing لذلك كتبت المقال بالاعتماد بشكل أكبر على مكتبات أخرى، لذلك أرجو تصجيحي ان أخطأت في مكان ما.
تحياتي

