สิงหาคม 20, 2019, 06:41:00 am *
ยินดีต้อนรับคุณ, บุคคลทั่วไป กรุณา เข้าสู่ระบบ หรือ ลงทะเบียน
ส่งอีเมล์ยืนยันการใช้งาน?

เข้าสู่ระบบด้วยชื่อผู้ใช้ รหัสผ่าน และระยะเวลาในเซสชั่น
   หน้าแรก   ช่วยเหลือ เข้าสู่ระบบ สมัครสมาชิก  
หน้า: [1]   ลงล่าง
  พิมพ์  
ผู้เขียน หัวข้อ: Delegate with Multithreading in GUI Application of C#  (อ่าน 6665 ครั้ง)
0 สมาชิก และ 1 บุคคลทั่วไป กำลังดูหัวข้อนี้
ShadowMan
Administrator
Hero Member
*****
ออฟไลน์ ออฟไลน์

เพศ: ชาย
กระทู้: 8272


ShadowWares


| |
« เมื่อ: มิถุนายน 22, 2012, 11:07:17 pm »

C# ถือได้ว่าเป็นเครื่องมือที่ช่วยให้โปรแกรมเมอร์มาความสะดวกในการสร้างงานขึ้นอย่างมหาศาล เนื่องจากทางผู้ออกแบบ (Microsoft)
รู้เท่าทันโปรแกรมเมอร์ทุกระดับชั้น และมองเห็นความเป็นมาและเป็นไปของโปรแกรมคอมพิวเตอร์ทุกรูปแบบที่เป็นไปได้
ด้วยเหตุนี้ จึงทำให้โปรแกรมเมอร์สร้างสรรค์งานได้ทุกรูปแบบอย่างง่ายดายในไม่กี่นาที
โดยส่วนตัวผมไม่คิดว่า C# ง่ายสำหรับผู้เริ่มต้น เพราะด้วยความง่ายในเชิงผู้ออกแบบโปรแกรม กับความง่ายของคนเขียนโปรแกรมมือใหม่มันแตกต่างกัน
ความง่ายในมุมของโปรแกรมเมอร์จะเกิดขึ้นจริง สำหรับคนที่มีประสบการณ์และคร่ำหวอดอยู่ในวงการ และ/หรือสายงานเท่านั้น เช่นโปรแกรมเมอร์คนหนึ่ง
คร่ำหวอดอยู่ในวงการ Networking พอหันไปจับงาน Graphics หรือ Computation ก็ต้องนั่งจับเจ่าเกาหัวกันพักใหญ่ กว่าจะทำให้งานง่ายได้อีกครั้ง
เพราะเนื้อหาของ C# เยอะมาก เมื่อเทียบกับ C/C++

มีคำถามเข้ามาทาง Email ผมหลายครั้งมาก ว่าด้วย GUI กับ โปรแกรมแบบ Multithreading ว่า "ไม่สามารถสั่ง Update GUI จากจาก Thread",
"สั่ง Upate GUI จาก Thread แล้วเกิด Runtime-Error" จะแก้อย่างไร จะทำอย่างไร??
วันนี้ถือโอกาศตอบปัญหาข้อนี้ผ่านทาง Forum ก็แล้วกัน เพราะตอบทาง email ไม่ไหว อีกทั้งตอบไปได้แบบสั้นๆ ไม่มีตัวอย่างให้ดู เกรงจะไม่ได้ช่วยอะไร
มาดูตัวอย่างโปรแกรมแบบเต็มๆ กันก่อนเลยครับ


Code: (c-sharp)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Collections;
namespace Thread1
{
    public partial class Form1 : Form
    {
        public delegate void UpdateGUICallback(ArrayList dataStructure);

        private int  counter = 0;
        private bool IsRunnuing;

        public Form1()
        {
            InitializeComponent();
        }
        
        public void UpdateUI(ArrayList dataStructure)
        {
            progressBar1.Value = (int)dataStructure[0];
            label1.Text = "counter = " + (string)dataStructure[1];
        }

        private void buttonStart_Click(object sender, EventArgs e)
        {
            if (IsRunnuing) return;
            Thread t = new Thread(ThreadFunc);
            IsRunnuing = true;
            buttonClose.Enabled = false;
            t.Start();
        }
        private void ThreadFunc()
        {
            while (IsRunnuing)
            {
                Thread.Sleep(100);
                if (++counter > 100) counter = 0;
                ArrayList dataStructure = new ArrayList();
                dataStructure.Add(counter);
                dataStructure.Add(counter.ToString());
                this.Invoke(new UpdateGUICallback(this.UpdateUI), dataStructure);
            }
        }
        private void buttonStop_Click(object sender, EventArgs e)
        {
            IsRunnuing = false;
            buttonClose.Enabled = true;
        }

        private void buttonClose_Click(object sender, EventArgs e)
        {
            IsRunnuing = false;
            Close();
        }
    }
}


โปรแกรมนี้ไม่มีอะไรมากไปกว่าสร้าง Thread ขึ้นมา แล้วทำงานวิ่งค่าของ counter จาก 0-100 วนไปเรื่อยๆ ทุกๆ 0.1 วินทที (100mS) และทำค่านี้โยนไปให้ ProgressBar และแสดงค่าของ counter ที่ Label
และนี่คือปัญหา เพราะถ้าเราเขียนค่าลงไปยัง GUI Component หรือ Control ใดๆ บน Form จะเกิด Exception หรือ Run-time error ขึ้นมาทันที
ทางแก้คือใช้ความสามรถของ Delegate โดยการประกาศขอใช้ความสามารถของ Delegate (ในมุมมองของภาษาซีอาจจะตีความให้เข้าใจง่ายๆ ว่า pointer to function นั่นเอง) รูปแบบคือ:

Code: (c-sharp)
public delegate void UpdateGUICallback(ArrayList dataStructure);

รูปแบบนี้ไม่ได้ตายตัว ยังคงว่ากันไปตามรูปแบบของภาษาซี C# ทุกประการ
เนื่องจาก GUI อาจจะมีหลายตัว อาจจะเพิ่มและ/หรือลดในตอนที่เรากำลังออกแบบโปรแกรมอยู่
เพื่อความสะดวกจึงออกแบบให้รับ parameter เป็น ArrayList และมองมันเป็น Data Structure

ต่อไปคือสร้างฟังก์ชั่นที่มีรูปแบบเดียวกับรูปแบบข้างต้นดังนี้


Code: (c-sharp)
public void UpdateUI(ArrayList dataStructure)
{
   progressBar1.Value = (int)dataStructure[0];
   label1.Text = "counter = " + (string)dataStructure[1];
}

ไม่มีอะไรมากกว่าอ่านอ่านค่าจาก ArrayList  และเขียนค่าไปยัง Control

ต่อไปใน Thread Function/Callback คือการเขียนค่าลงไปยัง ArrayList และ Invoke รายละเอียดคือ:

Code: (c-sharp)
private void ThreadFunc()
{
   while (IsRunnuing)
   {
      Thread.Sleep(100);
      if (++counter > 100) counter = 0;
      ArrayList dataStructure = new ArrayList();
      dataStructure.Add(counter);
      dataStructure.Add(counter.ToString());
      this.Invoke(new UpdateGUICallback(this.UpdateUI), dataStructure);
   }
}

ตัวสำคัญคือ:
Code: (c-sharp)
this.Invoke(new UpdateGUICallback(this.UpdateUI), dataStructure);

this คือตัวบ่งชี้ถึง class นี้นั่นเอง อาจจะ Control ตัวอื่นมาทำหน้าที่ Invoke แทนได้เช่น:
Code: (c-sharp)
progressBar1.Invoke(new UpdateGUICallback(this.UpdateUI), dataStructure);

หรือ
Code: (c-sharp)
label1.Invoke(new UpdateGUICallback(this.UpdateUI), dataStructure);


จะแบบไหนมันก็คือ pointer to function หรือ อ้างถึงสิ่งที่อยู่นอก Thread ได้เช่นกัน

นั่นเป็นเพียง 1 ในนับร้อย ของ delegate ส่วนตัวผมไม่ได้สนใจอะไรมากมายในความสามารถนี้ เพราะงานที่มีความซับซ้อน
ในระดับที่ต้องเล่นกับ Thread หรืออะไรที่ต้องมาวุ่นวายทำเรื่อง(เหมือน)ง่ายที่ไม่รู้ว่าข้างในมันทำอะไร มันรู้สึกไม่ปลอดภัย ผมจึงหันหน้าไปหา C/C++ ทันที

หวังเป็นอย่างยิ่งว่าจะเป็นคำตอบของคำถาม และจะเป็นความรู้เล็กๆ กับผู้ที่เข้ามาอ่านนะครับ
 cheesy

ผลการรันบน Simulator Windows CE and Windows Mobile Compatible:
[
บันทึกการเข้า

By SDW: Do No Wrong Is Do Nothing
          If you want to increase your success rate, double your failure rate
หน้า: [1]   ขึ้นบน
  พิมพ์  
 
กระโดดไป: