تقييم الموضوع :
  • 0 أصوات - بمعدل 0
  • 1
  • 2
  • 3
  • 4
  • 5
[مقال] Make Thread-Safe Calls to Windows Forms Controls
#1
السلام عليكم ورحمة الله وبركاته


{Make Thread-Safe Calls to Windows Forms Controls}
{كيف تصل الى الفورم وادواته من خلال Thread}




--



اليوم سنتحدث عن شيئ مهم وهو واجه او سيواجه اي مبرمج ، حقيقة قد طلب مني الاخ عبد الرحمن وضع هذه المقالة لأنه يعتقد انه لايزال الكثيرون يجهلون هذا الموضوع او قد يجهلون الThread اساسا ، بصراحا قد وضعت العنوان باللغة الانجلينزية لاني لم اجد ترجمة مناسبة لهذا العنوان .. فالافضل ان تبقى بعض المسميات على حالها .









اقسام المقالة


* نظرة سريعة عن الThread
* لماذا 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


وهكذا السيارة البطيئة ستمشي في مسار لوحدها ولن تعطل المسار الرئيسي Big Grin

 الفريم وورك قد خصص لنا Namespace كامل للتعامل مع الThread . بشكل عام، يمكنك انشاء ثريد فرعي بكل سهولة :-

PHP كود :
       private void button1_Click(object senderEventArgs 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 senderEventArgs e)
 
       {
 
           Thread downloadThread = new Thread(DonwloadFile);
 
           downloadThread.Start();
 
       }

 
       void DonwloadFile()
 
       {
 
           //ستتنفذ هذه الاكواد في الثريد الفرعي ولن تؤثر على مسار البرنامج
 
           this.Invoke((MethodInvoker)delegate
            
{
 
               button1.Text "Running..";
 
           });
 
       


الان لن يعترض عليك المترجم وسيصبح برنامجك في أمان لأن هذا الوصول يعتبر Thread-Safe Big Grin


كيف تستخدم 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 على الثريد الرئيسي وعدم انتظارها حتى تنتهي


لنظرب مثالا بهذا الكود :-

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
الرد }}}}
#2

موضوع منظم و جميل ما شاء الله عليك
شكرا جزيلا لك أخي محمد على تلبيتك لطلبي .. Blush
أتمنى لك التوفيق و السداد في حياتك دائما.
و بهذا تكون أول مرجع عربي على ما أعتقد عن هذه الخاصية Smile مبروك Big Grin
منقطع .. للدراسة Confused
الرد }}}}
تم الشكر بواسطة: الشاكي لله
#3
(05-01-14, 06:23 PM)mamas1 كتب :
موضوع منظم و جميل ما شاء الله عليك
شكرا جزيلا لك أخي محمد على تلبيتك لطلبي .. Blush
أتمنى لك التوفيق و السداد في حياتك دائما.
و بهذا تكون أول مرجع عربي على ما أعتقد عن هذه الخاصية Smile مبروك Big Grin

العفو حاضرين ..

ماهو المرجع الاول . ولكن اعتقد اني وضحت بشكل موجز فقط
الرد }}}}
تم الشكر بواسطة: mamas1 , Sajad
#4
السلام عليكم

بارك الله فيك وجزاك الله خيرا

تحياتي
الرد }}}}
تم الشكر بواسطة: الشاكي لله
#5
بـــــــــــارك الله فيـــــــــــــك ..
للعلم :
بنتعامل في الشركة بنفس   الطريقة  التى شرحتها  أنت ,    منذ سنوات  .. وهي فعاله  .

يعطيك العافيه .

Abu Ehab : Microsoft Partner  & Systems Developer
الرد }}}}
تم الشكر بواسطة: الشاكي لله
#6
مرحبا [b]الشاكي لله
و مرحبا بكل الموجودين مقال جميل وحقيقة ايرادك لمثال السيارة البطيئة
ازاح بعض الغموض والغشاوة عن الأعين
[/b]


اخي اذكر انه كان يوجد مثال للاخ رامي لوف ولا يحضرني الاسم لذلك المثال
المهم انه قام بتطبيق عملي على ال Thread والحقيقة انه التبس الامر علي حينها الى اليوم



وها هو الان قد جاء الوقت لكي احاول ان استدرك ما فاتني



اخي [b][b]الشاكي لله[/b] حبذا لو تدعمنا بمثال عملي بسيط بالمصدر يوضح الفروقات بين
[/b]

Thread
Thread-safe


وكل التوفيق للجميع
واخيرا شكرا لك على هالموضوع المهم
لا يلومني على انقطاعاتي المتكررة
فهي اما عمل او دراسة او تربية




سُبْحَانَكَ اللَّهُمَّ وَبِحَمْدِكَ، أَشُهَّدٌ أَنَّ لَا إلَهَ إلا أَنْتَ، أَسَتَغْفِرُكَ وَأَتُوبَ إِلَيْكَ
الرد }}}}
تم الشكر بواسطة:
#7
زادك الله علما وبارك لك فيه ارجوا ان تتطرق لكل جوانب لغة السي شارب نريد ان نكون محترفين على يدك هههههه ان شاء الله
الرد }}}}
تم الشكر بواسطة:
#8
ياجماعة الخير لقد جربت الكود

PHP كود :
Me.Invoke(New MethodInvoker(AddressOf Me.some)) 
لا اشكال بالوصول للادوات لكن يظهل الفورم جامد  لايتحرك حتى ينتهي العد

PHP كود :
Private Sub some()
       For a 0 To 1000 Step 1
           Label1
.Text a
           ProgressBar1
.Value a
           Threading
.Thread.Sleep(150)
       Next
   End Sub 
أستودعكم الله الذي لا تضيع ودائعه أرجو ان تجدوا بمشاركاتي ما يجعلكم تدعون لي بخير ان تحتم غيابي.
الرد }}}}
تم الشكر بواسطة:
#9
^
دالة some في الاعلى من الخطأ استدعائها بواسطة Invoke لان فيها loop وتاخير ..
عليك بعمل Invoke فقط للاكواد التي تتعامل مع الForm واداوته ، وهي في كودك :-

Label1.Text = a
ProgressBar1.Value = a


بامكانك لصق هذين الكودين في دالة جديدة مثلا (UpdateForm) وثم استدعاء UpdateForm بواسطة Invoke Smile
او الطريقة الافضل هي وضع الكودين في دالة مجهولة عشان تقدر توصل للبارمترات بسهولة :-
PHP كود :
Private Sub some()
 
      For 0 To 1000 Step 1

    Me
.Invoke(new MethodInvoker(Sub ()
 
          Label1.Text a
           ProgressBar1
.Value a
    End sub
))

 
          Threading.Thread.Sleep(150)
 
      Next
End Sub 


تذكر ، الدالة اعلاه قم باستدعائها بشكل طبيعي وليس باستعمال Invoke
الرد }}}}
تم الشكر بواسطة: سعود , سعود
#10
بارك الله فيك
ماهو كود التحقق ان الـ th مثلا  قيد العمل.
مثل
if backgroundworker is busy
أستودعكم الله الذي لا تضيع ودائعه أرجو ان تجدوا بمشاركاتي ما يجعلكم تدعون لي بخير ان تحتم غيابي.
الرد }}}}
تم الشكر بواسطة:


المواضيع المحتمل أن تكون متشابهة .
الموضوع : الكاتب الردود : المشاهدات : آخر رد
  [مقال] How to Make A splash screen in #C نور الدين وليد 1 121 01-12-16, 10:54 AM
آخر رد: afssac
  Autoscroll Controls Abu Ehab 1 175 11-12-15, 02:31 AM
آخر رد: myalsailamy
  نبذة سريعة مع مثال عن Thread oneyemenweb2 0 745 19-11-12, 05:48 PM
آخر رد: oneyemenweb2

التنقل السريع :


يقوم بقرائة الموضوع: بالاضافة الى ( 1 ) ضيف كريم