05-01-14, 03:52 PM
(آخر تعديل لهذه المشاركة : 03-12-16, 11:06 PM {2} بواسطة الشاكي لله.)
السلام عليكم ورحمة الله وبركاته
{Make Thread-Safe Calls to Windows Forms Controls}
{كيف تصل الى الفورم وادواته من خلال Thread}
{Make Thread-Safe Calls to Windows Forms Controls}
{كيف تصل الى الفورم وادواته من خلال Thread}
--
اليوم سنتحدث عن شيئ مهم وهو واجه او سيواجه اي مبرمج ، حقيقة قد طلب مني الاخ عبد الرحمن وضع هذه المقالة لأنه يعتقد انه لايزال الكثيرون يجهلون هذا الموضوع او قد يجهلون الThread اساسا ، بصراحا قد وضعت العنوان باللغة الانجلينزية لاني لم اجد ترجمة مناسبة لهذا العنوان .. فالافضل ان تبقى بعض المسميات على حالها .
اقسام المقالة
* نظرة سريعة عن الThread
* لماذا Thread-safe
* CheckForIllegalCrossThreadCalls
* استعمال Invoke وال Delegate لعمل Thread-Safe Calls
* مقارنة بسيطة ( Invoke Vs BeginInvoke )
* المراجع & الخاتمة
* لماذا Thread-safe
* CheckForIllegalCrossThreadCalls
* استعمال Invoke وال Delegate لعمل Thread-Safe Calls
* مقارنة بسيطة ( Invoke Vs BeginInvoke )
* المراجع & الخاتمة
نظرة سريعة عن الThread
الThread هو مسار تنفيذ ويلزم وجوده في اي برمجية في العالم ، فكل برنامج في العالم يحتوي على ثريد رئيسي .
دعنا نشبه ال Thread بالمسار في الشارع كما نعلم ان الشارع له مسارات (1 , 2 , 3 ...) حتى في الHighway تجد 4 مسارات او اكثر ، تخيل ان في الHighway يوجد مسار واحد فقط مالذي سيحصل ؟؟؟ النتيجة الطبيعية لهذا السؤال = ازدحام مروري
والموضوع نفسه يحصل في البرمجة ، لو انشأت دالة باسم DownloadFile وقمت بتنزيل ملف حجمه 1 غيغا بايت ، مالذي سيحصل ؟؟؟؟ لاحظ الصورة :-
فالوضع هنا مثله كمثل سيارة تمشي ببطأ وتعطل مسار السير في الشارع ، فالدالة DownloadFile ستأخذ وقت ريثما يتم تنزيل الملف ، في المقابل سيحصل للفورم Not Response ولايمكنك عمل اي شيئ فيه ، فهل ستقبل ان يحصل هذا للمستخدم ؟؟؟
اذن لماذا Internet download manger لايتعطل وهو يقوم بتنزيل الملفات ؟ نعم ، لانه يستعمل مسار اخر( Thread )لتنفيذ دالة Download File ، وهكذا اصبح لدينا مسارين :-
1- المسار الرئيسي UI thread
2- المسار الفرعي الذي يحتوي على دالة Download File
وهكذا السيارة البطيئة ستمشي في مسار لوحدها ولن تعطل المسار الرئيسي
الفريم وورك قد خصص لنا Namespace كامل للتعامل مع الThread . بشكل عام، يمكنك انشاء ثريد فرعي بكل سهولة :-
PHP كود :
private void button1_Click(object sender, EventArgs e)
{
Thread downloadThread = new Thread(DonwloadFile);
downloadThread.Start();
}
void DonwloadFile()
{
//ستتنفذ هذه الاكواد في الثريد الفرعي ولن تؤثر على مسار البرنامج
}
انا لن اشرح ال Thread اكثر من ذلك ، فهناك دروس كثيرة تتحدث عنه ، فأتمنى منك رؤيتها وتطبيقها كي تفهم القادم بشكل افضل
لماذا Thread-safe ؟
ماهو الThread-safe call اصلا ؟؟
عندما تحاول اسناد قيمة لاي اداة على الفورم او حتى اسناد قيمة للفورم نفسه وذلك من خلال الThread الفرعي
سيعطيك المترجم خطأ وهو خطأ مشهور :-
هذا الوصول يعتبر Unsafe-Thread call
وهذا الوصول يتم منعه افتراضي لأن الTextbox تم انشائها في Thread أخر وهو طبعا الثريد الرئيسي (UI thread) ، لماذا تم منعه وما الحكمة من ذلك ؟؟
Unsafe-Thread call له مخاطر جمة وعلى اثرها سيتدمر برنامجك ، سيحصل عدم استقرار للأداة ، وسيحصل bugs (اخطاء) تتعلق بالثريد ، وايضا قد تدخل الاداة في حالة جمود دائم . لهذا افتراضيا تم منع Unsafe-Thread call ولذلك اذا كنت تريد الوصول الى الاداة من خلال الثريد يجب عمل وصول آمن لها Thread-safe call
CheckForIllegalCrossThreadCalls
هذه خاصية معروفة للفورم وهي تقبل قيمة بولينية (True | False) ، ما وظيفة هذه الدالة ؟؟
جعل هذه الخاصية False يمنع ظهور خطأ Cross-Thread وهكذا يعتقد البعض انها حل لخطا Cross-thread ، لكن هذه الخاصية ستسبب لك مشاكل اكبر من ذاك الخطأ ، فجعل هذه الخاصية false تعني "إلغاء فحص الاستدعائات الغير شرعية" .. قلنا سابقا ان المترجم يفحص اذا كان هناك وصول Unsafe ويقوم باظهار خطأ ، ولكن جعل هذه الخاصية false يمنع المترجم من البحث عن الوصولات الغير أمنة . وبذلك فنحن قمنا بالاحتيال على المشكلة ولم نقم بحلها ونتيجة لذلك ..
إقتباس :No, that's not safe. The Winforms code that checks for threading mistakes is very important, the trouble that causes is extremely hard to diagnose. The biggest problem is that it doesn't cause consistent failure, your app will misbehave randomly and deadlock or crash only once a month. Or never at all, until you make a minor change. Or only on a particular user's machine, you'll blame the user instead of your code.
The only reason the CheckForIllegalCrossThreadCalls property exists in the first place is to keep .NET 2.0 and up compatible with buggy .NET 1.x programs where this threading safety test was not performed. Backward compatibility for bugs ;-P
ببساطة قد يتحطم برنامجك (Crash) او يصبح سلوكه غير معروف ويقوم بفعل اشياء على هواه او قد يدخل في حالة جمود .
السبب الوحيد لوجود خاصية CheckForIllegalCrossThreadCalls هو للتوافق مع NET 2.0 واعلى (صراحة لم افهم المقصود من كلامه جيدا)
المهم كيف سنجعل الوصول آمن ؟؟ هذا ماسنتطرق اليه من هنا الى نهاية المقالة .
استعمال Invoke وال Delegate لعمل Thread-Safe Calls
الدالة Invoke وهي موجودة في جميع الControls ، ووظيفتها هي تنفيذ كود على الThread الذي قام بإنشاء هذه الControl ، وهي توجد ايضا للفورم وهكذا سنستخدم ال Invoke الخاصة بالفورم فقط لأنها طبيعيا تؤدي الى نفس الثريد الذي قام بإنشاء الControls . وطريقة فعل ذلك :-
PHP كود :
this.Invoke((MethodInvoker)delegate
{
button1.Text = "Running..";
});
الكود كامل :-
PHP كود :
private void button1_Click(object sender, EventArgs e)
{
Thread downloadThread = new Thread(DonwloadFile);
downloadThread.Start();
}
void DonwloadFile()
{
//ستتنفذ هذه الاكواد في الثريد الفرعي ولن تؤثر على مسار البرنامج
this.Invoke((MethodInvoker)delegate
{
button1.Text = "Running..";
});
}
الان لن يعترض عليك المترجم وسيصبح برنامجك في أمان لأن هذا الوصول يعتبر Thread-Safe
كيف تستخدم Invoke :-
Invoke تريد بارمتار عبارة عن مفوض (Delegate) يشير الى الدالة التي تريدها ان تتنفذ في الثريد الرئيسي ، وانا بدل ان اقوم بتعريف Delegate جديد فقد استخدمت (MethodInvoker) وهي Delegate جاهزة .
يبقى السؤال لماذا استخدمت الكلمة المفتاحية delegate ؟؟
السبب هو اختصار الكود لاأكثر ، فdelegate هي كلمة مفتاحية لتعريف دالة مجهولة (بدون اسم) ، ولكن لن يضر لو استخدمت الطريقة التقليدية :-
PHP كود :
void DonwloadFile()
{
//ستتنفذ هذه الاكواد في الثريد الفرعي ولن تؤثر على مسار البرنامج
this.Invoke(new MethodInvoker(DoChangeText));
}
void DoChangeText()
{
button1.Text = "Running..";
}
ولكن الافضل استخدام دالة مجهولة فهي اسرع ومختصرة اكثر .
مقارنة بسيطة ( Invoke Vs BeginInvoke )
Invoke و BeginInvoke دالتان تؤديان نفس الوظيفة فا
Invoke : تنفيذ دالة الDelegate على الثريد الرئيسي وانتظارها حتى تنتهي
BeginInvoke : تنفيذ دالة ال Delegate على الثريد الرئيسي وعدم انتظارها حتى تنتهي
Invoke : تنفيذ دالة الDelegate على الثريد الرئيسي وانتظارها حتى تنتهي
BeginInvoke : تنفيذ دالة ال Delegate على الثريد الرئيسي وعدم انتظارها حتى تنتهي
لنظرب مثالا بهذا الكود :-
PHP كود :
void DonwloadFile()
{
//ستتنفذ هذه الاكواد في الثريد الفرعي ولن تؤثر على مسار البرنامج
this.Invoke((MethodInvoker)delegate
{
button1.Text = "Running..";
System.Threading.Thread.Sleep(2000);
});
//سيتم انتظار المقوض السابق حتى ينتهي
MessageBox.Show("Hello");
}
في هذا الكود سيتم اظهار الرسالة بعد مرور ثانيتين ، لان الdelegate لم ينتهي قبل مرور ثانيتين فأنا وضعت (System.Threading.Thread.Sleep(2000 لتأخير اكتمال الDelegate
اذن Invoke لا تذهب للأكواد التي بعدها اذا الdelegate لم ينتهي
في حالة BeginInvoke سيتم اظهار الرسالة فورا ، فلن يتم انتظار الdelegate حتى ينتهي بل سيتم اكمال الاكواد التي تحت BeginInvoke بالتوازي مع الdelegate
المراجع & الخاتمة
How to: Make Thread-Safe Calls to Windows Forms Controls
C# CheckForIllegalCrossThreadCalls
What's the difference between Invoke and BeginInvoke
آمل اني قد وفقت في سرد المعلومة بشكل صحيح ، وايضا كل الي ذكرناه ماهو الى القليل فقط عن الThread فهو بحر واسع .
على العموم ، موفقين شباب
تحياتي - Done by Alshaki LLah
C# CheckForIllegalCrossThreadCalls
What's the difference between Invoke and BeginInvoke
آمل اني قد وفقت في سرد المعلومة بشكل صحيح ، وايضا كل الي ذكرناه ماهو الى القليل فقط عن الThread فهو بحر واسع .
على العموم ، موفقين شباب
تحياتي - Done by Alshaki LLah