مسارات التنفيذ مع Windows Forms

الناقل : elmasry | الكاتب الأصلى : تركي العسيري | المصدر : www.al-asiri.com

في مدونتي الأخيرة تحدثت عن مشكلة دمج مبدأ مسارات التنفيذ المتعددة Multi-Threading مع نماذج وأدوات Windows Forms، فلو كانت لدينا نافذة نموذج بها أداة TextBox وكتب في أحد سطورها الإجراء التالي:


Basic:
Private Sub changeText()
      Me.TextBox1.Text = Date.Now.ToString()
End Sub

C#:
private void changeText()
{
      this.TextBox1.Text = System.DateTime.Now.ToString();
}

فالمشكلة ليست في الإجراء بقدر ما هي في مسار التنفيذ Thread الذي سيستدعيه، فلو كان مسار التنفيذ هو نفسه مسار التنفيذ الذي أنشأ النافذة (وبالتالي الأداة)، فسيتم تنفيذ الاجراء بكل لطافة ونعومة:

Basic:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
      Me.changeText()
End Sub



C#:
private void Button1_Click(object sender, EventArgs e)
{
      this.changeText();
}

ولكن إن حاولت طلب اللجوء السياسي الى مسار تنفيذ آخر:

Basic:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
      With New Threading.Thread(AddressOf Me.changeText)
            .Start()
      End With
End Sub



C#:
private void Button2_Click(object sender, EventArgs e)
{
      new Thread(new ThreadStart(this.changeText)).Start();
}

فالويل كل الويل لك! وستظهر رسالة خطأ تخبرك بعدم قدرتك على الوصول الى أعضاء الأداة TextBox1 من مسار تنفيذ آخر.

من هنا نستطيع حماية الإجراء changeText()‎ وعدم تنفيذه في حالة تم استدعائه من قبل مسار تنفيذ اخر، يتم ذلك (بكل بساطة) بالتحقق من الخاصية InvokeRequired:

Basic:
Private Sub changeText()
      If Me.TextBox1.InvokeRequired Then
            MsgBox("انت تحاول الوصول الى الاداة من مسار تنفيذي اخر.")
      Else
            Me.TextBox1.Text = Date.Now.ToString()
      End If
End Sub



C#:
private void changeText()
{
      if (this.TextBox1.InvokeRequired)
            MessageBox.Show("انت تحاول الوصول الى الاداة من مسار تنفيذي اخر.");
      else
            this.TextBox1.Text = DateTime.Now.ToString();
}

ستعود الخاصية InvokeRequired بالقيمة True ان تم استدعاء الاجراء من قبل مسار تنفيذ آخر.



شكرا ولكن لازت مصمم على الوصول الى الأداة
ان كنت لازلت مصمم ولن تنام الليلة حتى تصل الى الاداة من خلال مسار تنفيذ آخر، فمن حسن حظك أنه توجد ثلاث طرق BeginInvoke(), Invoke(), EndInvoke()‎ لديها واسطة قوية لدرجة انه يمكنك استدعائها حتى لو كانت من قبل مسار تنفيذي آخر (عليك الإعتماد على التفويض Delegates فهو أحد متطلبات الطريقة Invoke()‎):


Basic:
Delegate Sub dlgZeroParamSub()
Private Sub changeText()
      If Me.TextBox1.InvokeRequired Then
            MsgBox("انت تحاول الوصول الى الاداة من مسار تنفيذي اخر.")
            Dim d As New dlgZeroParamSub(AddressOf Me.changeText)
            Me.TextBox1.Invoke(d)

      Else
            Me.TextBox1.Text = Date.Now.ToString()
      End If
End Sub



C#:
public delegate void dlgZeroParamSub();
private void changeText()
{
      if (this.TextBox1.InvokeRequired)
      {
            MessageBox.Show("انت تحاول الوصول الى الاداة من مسار تنفيذي اخر.");
            Form1.dlgZeroParamSub sub1 = new Form1.dlgZeroParamSub(this.changeText);
            this.TextBox1.Invoke(sub1);

      }
      else
      {
            this.TextBox1.Text = DateTime.Now.ToString();
      }
}




اضغط هنا لانزال هذا المثال.




لا تقرأ هذه الفقرة!
حتى وان لم تقرأ السطور السابقة للمدونة، ففضولك الإنساني الفطري سيجبرك على قراءة هذه الفقرة التي نصحتك بعدم قراءتها، والسبب أنه علم مضر جدا وقد يجعلك ترمي كل ذكرته عرض الحائط، فالكود الفاشل التالي:

Basic:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
      With New Threading.Thread(AddressOf Me.changeText)
            .Start()
      End With
End Sub

Private Sub changeText()
      Me.TextBox1.Text = Date.Now.ToString()
End Sub



C#:
private void Button2_Click(object sender, System.EventArgs e)
{
       new Thread(new ThreadStart(this.changeText)).Start();
}

private void changeText()
{
      this.TextBox1.Text = System.DateTime.Now.ToString();
}

كما تعلم أنه سيظهر رسالة خطأ (بسبب الوصول الى احد خصائص الأداة من مسار تنفيذي آخر)، ولكن هل تصدق أن هذا الخطأ ((قد)) لا يظهر عند تنفيذ البرنامج بدون المنقح Debugger! جرب وشغل الملف التنفيذي EXE للبرنامج (مباشرة من مستكشف النظامWindows Explorer) او اختر الأمر Debug->Start Without Debugging أو نفذ البرنامج بالضغط على المفاتيح [Ctrl + F5]، ستفاجئ أن البرنامج تم تنفيذه وكأن شيئا لم يحدث!

وحتى لو كان المنقح Debugger مفعل، يمكن إسناد القيمة False للخاصية المشتركة CheckForIllegalCrossThreadCalls التابعة للفئة Control ولن تظهر رسالة الخطأ:


Basic:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
      Control.CheckForIllegalCrossThreadCalls = False
      With New Threading.Thread(AddressOf Me.changeText)
            .Start()
      End With
End Sub



C#:
private void Button2_Click(object sender, System.EventArgs e)
{
      Control.CheckForIllegalCrossThreadCalls = false;
      new Thread(new ThreadStart(this.changeText)).Start();
}

ما يحدث هنا انك تطلب تجاهل مسألة التحقق من تنفيذ الأدوات من خلال مسارات تنفيذ متعددة (بالرغم من الادوات ليست أمنة للمسارات Not Thread Safe)، وتنصحك Microsoft، وكاتب هذه السطور، وجميع مبرمجي‎.NET بعدم التفكير مطلقا وابدا ابدا ابدا في تطبيق هذه الأفكار المسمومة والمتطرفة، فهي ستؤدي إلى كوارث ومصائب برمجية وانهيارات Fatal Errors لا يعلم عاقبتها الا رب العالمين!

لم أرد أن اخبرك بهذه الفقرة (وقد عنونتها بعدم قراءتها)، ولكن ضميري البرمجي أجبرني على اخبارك بهذه المعلومة التي اتمنى ان لا تفكر فيها.


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

كفانا الله شر مسارات التنفيذ المتعددة Multi-Threading، والله لا يحوجنا لها في مشاريعنا القادمة!

-- تركي