Most popular

วันพฤหัสบดีที่ 26 พฤษภาคม พ.ศ. 2554

ใช้ Lock ทำงานที่ละ Thread

ส่วนมากการทำงานของโปรแกรมที่เขียนแบบ Multi-Threading จะมีจุดประสงค์ให้ทำงานพร้อมๆกันในเวลาเดียว
แต่ก็มีหลายกรณีที่จำเป็นต้องบังคับให้ Thread ทำงานตามๆกันไปโดยไม่พร้อมกัน การทำให้ Thread ทำงานจนจบก่อนที่จะไปทำที่ Thread อื่น
ผู้เขียนจะทำตัวอย่างโปรแกรมโดยใช้ คำสั่ง lock(Instance){} เพื่อเป็นการทำให้การทำงาน Thread ทำงานเสร็จก่อน โดยจะมี Control ProgressBar 4 ตัว ที่จะทำงานตามลำดับกันไป GUI ตามภาพด้านล่าง

ผู้เขียนจะไม่ทำงานตั้งค่าเริ่มต้นของแต่ละ Control .ในขั้นตอน Design ที่จะทำการตั้งค่าทั้งหมดตอน Runtime โดยที่
  • ฟอร์ม ตั้งชื่อ threadSEQ
  • ปุ่ม Start ตั้งชื่อ StartBtn
  • ProgressBar ทั้ง 4 ให้มีชื่อเป็น Default
ผู้เขียนสร้าง Type ขึ้นมา 2 กลุ่มคือ

enum setprogress { spReset,spStart}
enum setThread{stCreate,stAbort}

Member ของ Class มีอยู่ตัวเดียวคือ
private Thread[] t;
t เป็น Member ที่เป็น array ของ Thread ทั้งหมด
method ที่เป็นการกำหนดค่าต่างๆ และทำให้ Thread ต่างๆ ถูกสร้างขึ้นคือ
        //method setAllProgress มี parameter เป็น Type  setprogress  ที่สร้างขึ้นตอนแรก และมี Pass by referance นับค่า Thread ที่เกิดขึ้น
        private void setAllProgress(setprogress value,ref int ThreadPGCount)
        {
            //ให้เริ่มต้นนับ Thread เป็น 0
            ThreadPGCount = 0;
            //สร้าง Object Random เพื่อใช้ในการสุ่มค่าสูงสุดของ ProgressBar
            Random rd = new Random();

            //วนลูป Control ทั้งหมดที่อยู่บนฟอร์ม
            foreach (Control c in this.Controls)
            {
                //ถ้า Control ที่พบใช่ชนิด ProgressBar 
                if (c is ProgressBar)
                {
                    ProgressBar p = (ProgressBar)c;
                    //ตรวจสอบคำสั่งที่ให้ทำงาน
                    if (value == setprogress.spReset)
                    {
                        p.Minimum = 0;
                        p.Maximum = rd.Next(100,500);
                        p.Step = 1;
                        p.Value = 0;
                    }
                    else if (value == setprogress.spStart)
                    {
                        this.t[ThreadPGCount].Start(p);
                    }

                    //นับจำนวน Thread ที่จะใช้ทำงาน
                    ThreadPGCount++;
                }
            }
        }

method runprogress การทำงานของ Thread

        private void runprogress(object o)
        {
            //แปลงค่า Control ProgressBar ที่ส่งเข้ามา
            ProgressBar pg = (ProgressBar)o;

            lock (this)
            {
                while (pg.Value < pg.Maximum)
                {
                    pg.PerformStep();
                    Thread.Sleep(10);
                    this.Text = "Runing " + pg.Value + "(" + pg.Maximum + ")";
                    this.Update();
                }
            }
        }

        private void setAllThread(setThread value)
        {
            //วนลูป Thread ทั้งหมด
            for (int i = 0; i < this.t.Length; i++)
            {
                if (value == setThread.stCreate) //ให้สร้าง Thread
                    this.t[i] = new Thread(this.runprogress);
                else if (value == setThread.stAbort) //ให้จบการทำงานของ Thread
                    this.t[i].Abort();
            }
        }
method ต่างๆนำไปใช้งานภายใน Constructor เพื่อให้กำหนดค่าทั้งหมด
        public threadSEQ()
        {
            InitializeComponent();
            threadSEQ.CheckForIllegalCrossThreadCalls = false;

            int tid = 0;  //เก็บจำนวน Thread ที่ต้องใช้
            this.setAllProgress(setprogress.spReset,ref tid);

            this.t = new Thread[tid]; //สร้าง Thread array ตามจำนวน Thread ที่ต้องใช้
            this.setAllThread(setThread.stCreate);
        }
ปุ่ม Start เรียกใช้ method ที่ได้สร้างขึ้นมาแล้ว
        private void StartBtn_Click(object sender, EventArgs e)
        {
            int i=0;
            this.setAllProgress(setprogress.spStart,ref i);
        }
ที่จะลืมไม่ได้ก็คือตอนมีการปิดฟอร์ม ก็ต้องให้ Thread หยุดทำงานทั้งหมด เพื่อป้องกันการทำงานของ Thread ที่อาจจะค้างอยู่ได้
        private void Form2_FormClosed(object sender, FormClosedEventArgs e)
        {
            this.setAllThread(setThread.stAbort);
        }
ภาพรวมของ Class ทั้งหมด


ทดสอบการทำงานของโปรแกรมเมื่อกดปุ่ม Start 
ProgressBar แต่ละตัวก็จะทำงานโดยรอทำงานตามกันไป ไม่ทำงานพร้อมกัน



Code ทั้งหมดของฟอร์ม threadSEQ
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace testThread
{
    public partial class threadSEQ : Form
    {
        enum setprogress { spReset,spStart}
        enum setThread{stCreate,stAbort}

        private Thread[] t;

        public threadSEQ()
        {
            InitializeComponent();
            threadSEQ.CheckForIllegalCrossThreadCalls = false;
            int tid = 0;
            this.setAllProgress(setprogress.spReset,ref tid);

            this.t = new Thread[tid];
            this.setAllThread(setThread.stCreate);
        }

        private void setAllProgress(setprogress value,ref int ThreadPGCount)
        {
            ThreadPGCount = 0;
            Random rd = new Random();
            foreach (Control c in this.Controls)
            {
                if (c is ProgressBar)
                {
                    ProgressBar p = (ProgressBar)c;
                    if (value == setprogress.spReset)
                    {
                        p.Minimum = 0;
                        p.Maximum = rd.Next(100,500);
                        p.Step = 1;
                        p.Value = 0;
                    }
                    else if (value == setprogress.spStart)
                    {
                        this.t[ThreadPGCount].Start(p);
                    }
                    ThreadPGCount++;
                }
            }
        }

        private void setAllThread(setThread value)
        {
            for (int i = 0; i < this.t.Length; i++)
            {
                if (value == setThread.stCreate)
                    this.t[i] = new Thread(this.runprogress);
                else if (value == setThread.stAbort)
                    this.t[i].Abort();
            }
        }

        private void runprogress(object o)
        {
            ProgressBar pg = (ProgressBar)o;

            lock (this)
            {
                while (pg.Value < pg.Maximum)
                {
                    pg.PerformStep();
                    Thread.Sleep(10);
                    this.Text = "Runing " + pg.Value + "(" + pg.Maximum + ")";
                    this.Update();
                }
            }
        }

        private void StartBtn_Click(object sender, EventArgs e)
        {
            int i=0;
            this.setAllProgress(setprogress.spStart,ref i);
        }

        private void Form2_FormClosed(object sender, FormClosedEventArgs e)
        {
            this.setAllThread(setThread.stAbort);
        }
    }
}

วันอังคารที่ 24 พฤษภาคม พ.ศ. 2554

สอนน้องทำเครื่องคิดเลขด้วย Delphi

น้องๆที่หัดเขียนโปรแกรม หรือกำลังศึกษาอยู่ บางคนก็ยังไม่ผ่านกับแนวคิดการทำเครื่องคิดเลขเสมือนจริง
การทำเครื่องคิดเลขนั้นไม่ใช่การเขียนโปรแกรมที่หนักหนาอะไร เพียงแต่เป็นการฝึกการใช้แนวคิด ทดสอบความเข้าใจ ในเรื่อง Operator และ ตัวแปรเก็บค่า (Variable) ยังไม่ได้แตะหลักการใดๆ ในการทำงานโปรแกรมจริงๆกันเลย

จากนี้ผู้เขียนจะเขียนโปรแกรมเครื่องคิดเลขด้วยการทำงานง่ายๆ โดยผู้เขียนจะใช้ Delphi Version 2010 ในการสาธิต
แน่นอนว่าต้องสร้าง GUI ของเครื่องคิดเลขเสียก่อน ตามภาพครับ

Form1 และชื่อ Control ที่สำคัญ
 
Control Edit ที่ใช้แสดง Operator
โครงสร้างของ Class และ Interface

  TForm1 = class(TForm)
    ResultDisplay: TEdit; //จอแสดงผล
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    Button5: TButton;
    Button6: TButton;
    Button7: TButton;
    Button8: TButton;
    Button9: TButton;
    clearBtn: TButton; //ปุ่มเคลียร์ค่า
    Button11: TButton;
    amountBtn: TButton; //ปุ่ม =
    PLUSBTN: TButton; //ปุ่ม +
    MINUSBTN: TButton; //ปุ่ม -
    MULTIPLYBTN: TButton; //ปุ่ม * (คูณ)
    DIVBTN: TButton; //ปุ่ม % (หาร)
    oprDisplay: TEdit; //แสดงผล Operator
    Button13: TButton;
    //method ที่ใช้แทน event ทั้งหมดของปุ่ม Operator ต่างๆ
    procedure setOPR(Sender: TObject);
    //method ที่ใช้แทน event ของปุ่มตัวเลข และจุด
    procedure setNumber(Sender: TObject);
    
    procedure clearBtnClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure amountBtnClick(Sender: TObject);
  private
    { Private declarations }
    opr: char;  //เก็บค่า Operator
    tempRes: double; //เก็บผลการคำนวณ
  public
    { Public declarations }
  end;

method ที่สำคัญ 2 method คือ setOPR และ setNumber ในส่วนของ Implement Code ตามนี้


procedure TForm1.setOPR(Sender: TObject);
begin
     //ถ้าไม่ได้ระบุ Operator ให้ คำนวณผลลัพธ์ก่อน
     if Self.opr<>' ' then Self.amountBtn.Click;
     
     //ตรวจสอบว่ากดปุ่ม Operator ใด
     if TButton(Sender)=Self.PLUSBTN then Self.opr:='+' else
     if TButton(Sender)=Self.MINUSBTN then Self.opr:='-' else
     if TButton(Sender)=Self.MULTIPLYBTN then Self.opr:='x' else
     if TButton(Sender)=Self.DIVBTN then Self.opr:='%' ;


     //แสดง Operator ให้ผู้ใช้เห็น
     Self.oprDisplay.Text:=Self.opr;


     //เก็บค่าเก่าที่แสดงค้างอยู่
     Self.tempRes:=StrToFloat(Self.ResultDisplay.Text);
     //ล้างค่าที่แสดงอยู่
     Self.ResultDisplay.Text:='';
end;


จากนั้นกำหนดให้ Event OnClick ของปุ่ม Operator ทั้งหมด 4 ปุ่ม คือ บวก ลบ คูณ หาร ใช้ method setOPR



procedure TForm1.setNumber(Sender: TObject);
Var
     Value: String;
begin
     //เก็บค่า Caption ของปุ่มที่โดนกด
     Value:=TButton(Sender).Caption;


     //ตรวจสอบว่า Caption เป็น . (dot) หรือไม่
     if Value='.' then
     Begin
        //ถ้าเป็น . (dot) และมีอยู่ในการแสดงผลแล้ว ให้จบการทำงานทันที
        if  PosEx('.',Self.ResultDisplay.Text,1) > 0 then Abort;
     end;


     //เพิ่ม Caption เพื่อแสดงผล
     Self.ResultDisplay.Text:=Self.ResultDisplay.Text+Value;
end;

จากนั้นก็กำหนดให้ Event OnClick ทั้ง 11 ปุ่ม คือ 0 ถึง 9 และ . (dot) ใช้ method setNumber


สิ่งที่ไม่ควรลืมก็คือ การให้ค่าเริ่มต้น (Default) กับตัวแปร (Variable) ที่เป็น Member ของ Class ด้วย


procedure TForm1.FormCreate(Sender: TObject);
begin
     Self.tempRes:=0;
     Self.opr:=' ';
end;

Code ภายในปุ่มเคลียร์ค่าก็ไม่มีอะไรมาก แค่ให้ค่าทุกอย่างตั้งต้นใหม่่


procedure TForm1.clearBtnClick(Sender: TObject);
begin
     Self.ResultDisplay.Text:='0';
     Self.tempRes:=0;
     Self.opr:=' ';
end;

ส่วนของผลลัพธ์ หรือปุ่ม = ให้คำนวณค่าเป็นทศนิยมได้


procedure TForm1.amountBtnClick(Sender: TObject);
begin
     if Self.opr='+' then Self.tempRes:=Self.tempRes + StrToFloat(Self.ResultDisplay.Text) else
     if Self.opr='-' then Self.tempRes:=Self.tempRes - StrToFloat(Self.ResultDisplay.Text) else
     if Self.opr='x' then Self.tempRes:=Self.tempRes * StrToFloat(Self.ResultDisplay.Text) else
     if Self.opr='%' then Self.tempRes:=Self.tempRes / StrToFloat(Self.ResultDisplay.Text) ;


     Self.oprDisplay.Text:='';
     Self.opr:=' ';
     Self.ResultDisplay.Text:=FormatFloat('#,##0.00',Self.tempRes);
end;


ทดสอบรันโปรแกรมเป็นอันเสร็จสิ้นการเขียนโปรแกรมเครื่องคิดเลขแบบง่ายๆ รวบรัด

ทดสอบ 5 + 0.5= 5.50
ทดสอบ 23.88 + 14.30 + 8 = 46.18

Source Code ของ Unit1.pas(Form1)

unit Unit1;


interface


uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls,StrUtils;


type
  TForm1 = class(TForm)
    ResultDisplay: TEdit;
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    Button5: TButton;
    Button6: TButton;
    Button7: TButton;
    Button8: TButton;
    Button9: TButton;
    clearBtn: TButton;
    Button11: TButton;
    amountBtn: TButton;
    PLUSBTN: TButton;
    MINUSBTN: TButton;
    MULTIPLYBTN: TButton;
    DIVBTN: TButton;
    oprDisplay: TEdit;
    Button13: TButton;
    procedure setOPR(Sender: TObject);
    procedure setNumber(Sender: TObject);
    procedure clearBtnClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure amountBtnClick(Sender: TObject);
  private
    { Private declarations }
    opr: char;
    tempRes: double;
  public
    { Public declarations }
  end;


var
  Form1: TForm1;


implementation


{$R *.dfm}


{ TForm1 }


procedure TForm1.setNumber(Sender: TObject);
Var
     Value: String;
begin
     Value:=TButton(Sender).Caption;


     if Value='.' then
     Begin
        if  PosEx('.',Self.ResultDisplay.Text,1) > 0 then Abort;
     end;


     Self.ResultDisplay.Text:=Self.ResultDisplay.Text+Value;
end;


procedure TForm1.setOPR(Sender: TObject);
begin
     if Self.opr<>' ' then Self.amountBtn.Click;
     
     if TButton(Sender)=Self.PLUSBTN then Self.opr:='+' else
     if TButton(Sender)=Self.MINUSBTN then Self.opr:='-' else
     if TButton(Sender)=Self.MULTIPLYBTN then Self.opr:='x' else
     if TButton(Sender)=Self.DIVBTN then Self.opr:='%' ;


     Self.oprDisplay.Text:=Self.opr;
     Self.tempRes:=StrToFloat(Self.ResultDisplay.Text);
     Self.ResultDisplay.Text:='';
end;


procedure TForm1.clearBtnClick(Sender: TObject);
begin
     Self.ResultDisplay.Text:='0';
     Self.tempRes:=0;
     Self.opr:=' ';
end;


procedure TForm1.FormCreate(Sender: TObject);
begin
     Self.tempRes:=0;
     Self.opr:=' ';
end;


procedure TForm1.amountBtnClick(Sender: TObject);
begin
     if Self.opr='+' then Self.tempRes:=Self.tempRes + StrToFloat(Self.ResultDisplay.Text) else
     if Self.opr='-' then Self.tempRes:=Self.tempRes - StrToFloat(Self.ResultDisplay.Text) else
     if Self.opr='x' then Self.tempRes:=Self.tempRes * StrToFloat(Self.ResultDisplay.Text) else
     if Self.opr='%' then Self.tempRes:=Self.tempRes / StrToFloat(Self.ResultDisplay.Text) ;


     Self.oprDisplay.Text:='';
     Self.opr:=' ';
     Self.ResultDisplay.Text:=FormatFloat('#,##0.00',Self.tempRes);
end;


end.

วันอาทิตย์ที่ 22 พฤษภาคม พ.ศ. 2554

เริ่มต้นพัฒนา Thread Object ใน Delphi (2/2)

จากตอนที่แล้วผู้เขียนได้ทดสอบสร้าง Thread Object หรือก็คือ Unit ที่บรรจุ Thread Class ไปแล้ว
เริ่มต้นพัฒนา Thread Object ใน Delphi (1/2) (http://systemdevman.blogspot.com/2011/05/thread-object-delphi-12.html)

ผู้เขียนจะปรับปรุง Code จากตัวอย่างที่แล้ว โดยสร้าง Thread Class เข้าไปใน Unit เดียวกับฟอร์ม
ก็จะมีความง่ายกว่าในแบบแรก เพราะ Thread สามารถใช้งานองค์ประกอบต่างๆที่อยู่บนฟอร์มได้ ไม่ว่าจะเป็น Control ต่างๆ หรือ method ของที่อยู่ใน Class ของ Form
ผู้เขียนจะปรับเปลี่ยนให้ Class แสดงอายุของพนักงานแต่ละท่านออกมาโดยคำนวณจากวันเกิด
Thread Class ที่สร้างขึ้นมีเพียง method Execute เพื่อใช้ทำงานเท่านั้น

TthdEmployeeAge = class(TThread)
protected
     procedure Execute; override;
end;


ภายใน method Execute มีการปรับปรุงเล็กน้อย

procedure TthdEmployeeAge.Execute;
Var
        itemText: String;
        birth: TDateTime;
        age: Integer;
begin
       inherited;
       with form1.DataSource1.DataSet do
       Begin

             First; //ไปที่ข้อมูลแถวแรกก่อน
             form1.ListBox1.Clear; //ล้างข้อมูลใน ListBox
             while not Eof do
             Begin

                  itemText:='รหัสพนักงาน '+FieldByName('EMP_COD').AsString;
                  itemText:=itemText + ' ชื่อ '+ FieldByName('EMP_NAM1').AsString + FieldByName('EMP_NAM2').AsString;
                  itemText:=itemText + ' นามสกุล '+ FieldByName('EMP_NAM3').AsString;

                  //เก็บค่าวันเเกิด
                  birth:=FieldByName('EMP_BRTH').AsDateTime;
                  //คำนวณอายุ
                  age:=YearOf(Now) - YearOf(birth);

                 itemText:=itemText+' อายุ '+IntToStr(age)+' ปี';
                 form1.ListBox1.Items.Add(itemText);
                 Next;
             end;
       end;
end;

การเรียกใช้ จาก Event FormShow() ก็ใช้ Constructor ตัวเดิมที่ Inheritance มาจาก TThread

procedure TForm1.FormShow(Sender: TObject);
begin
       TthdEmployeeAge.Create(false);
end;

ลองทดสอบโปรแกรมก็จะได้ผลคล้ายเดิม แต่จะมีการคำนวณอายุของพนักงานออกมาแทนเพศ


สรุป
การสร้าง Thread Class ขึ้นมาใช้แบบง่ายๆ ทั้งสองแบบนี้ ต่างก็เหมาะสมกับการทำงานแต่ละอย่าง แต่ละจุดประสงค์ Thread Object ซึ่งเป็นไฟล์ Unit แบ่งออกมาชัดเจนนั้นก็จำเป็นต้องสร้างให้มีความยืดหยุ่น ให้มีการทำงานได้จากหลายๆ ที่ในโปรแกรม ส่วนการเขียน Thread ลงไปใน Unit ที่มีอยู่แล้วนั้น อาจจะนำไปใช้ในการเฉพาะของ Unit นั้นๆ เขียนง่ายๆ ทำงานเฉพาะหน้าที่เดียว จึงไม่จำเป็นต้องมีความยืดหยุ่นมาก

Source Code ภายใน Unit1.pas หรือ Form1
unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, DB, ADODB;

type
      TthdEmployeeAge = class(TThread)
      protected
           procedure Execute; override;
      end;

TForm1 = class(TForm)
     ADOConnection1: TADOConnection;
     ADOTable1: TADOTable;
     DataSource1: TDataSource;
     ADOTable1EMP_COD: TStringField;
     ADOTable1EMP_NAM1: TStringField;
     ADOTable1EMP_NAM2: TStringField;
     ADOTable1EMP_NAM3: TStringField;
     ADOTable1EMP_SEX: TStringField;
     ADOTable1EMP_BRTH: TDateTimeField;
     ListBox1: TListBox;
     procedure FormShow(Sender: TObject);
private
    { Private declarations }
public
    { Public declarations }
end;

var
     Form1: TForm1;

implementation

uses DateUtils;

{$R *.dfm}

procedure TForm1.FormShow(Sender: TObject);
begin
           TthdEmployeeAge.Create(false);
end;


{ TthdEmployeeAge }


procedure TthdEmployeeAge.Execute;
Var
        itemText: String;
        birth: TDateTime;
        age: Integer;
begin
       inherited;
       with form1.DataSource1.DataSet do
       Begin

             First; //ไปที่ข้อมูลแถวแรกก่อน
             form1.ListBox1.Clear; //ล้างข้อมูลใน ListBox
             while not Eof do
             Begin

                  itemText:='รหัสพนักงาน '+FieldByName('EMP_COD').AsString;
                  itemText:=itemText + ' ชื่อ '+ FieldByName('EMP_NAM1').AsString + FieldByName('EMP_NAM2').AsString;
                  itemText:=itemText + ' นามสกุล '+ FieldByName('EMP_NAM3').AsString;

                  //เก็บค่าวันเเกิด
                  birth:=FieldByName('EMP_BRTH').AsDateTime;
                  //คำนวณอายุ
                  age:=YearOf(Now) - YearOf(birth);

                 itemText:=itemText+' อายุ '+IntToStr(age)+' ปี';
                 form1.ListBox1.Items.Add(itemText);
                 Next;
             end;
       end;
end;

end.

วันเสาร์ที่ 21 พฤษภาคม พ.ศ. 2554

เริ่มต้นพัฒนา Thread Object ใน Delphi (1/2)

เรื่องของ Multi-Threading ใน Delphi ไม่ใช่เรื่องยาก ถ้าเข้าใจแล้วออกจะง่ายเสียด้วย เพราะตามโครงสร้างของภาษา Object Pascal ที่มีลักษณะแบ่งการเขียน Code อย่างชัดเจน
ผู้เขียนได้มีโอกาสใช้ Thread Object อยู่บ่อยครั้ง เนื่องจากต้องการที่จะลดภาระของ Process บางอย่างของ Server และทำให้เป็น User Friendly มากขึ้นเพราะโปรแกรมจะไม่ต้องหยุดรอ ก็เปรียบได้กับการใช้ Ajax บนเว็บ
ผู้เขียนจะทดสอบสร้าง Thread Object 2 แบบด้วยกันโดยใช้ Delphi Version 7
  1. แบบแรกคือการสร้าง Thread Class โดยแยกออกมาจากไฟล์ Unit อื่นๆ
  2. แบบที่สองคือการใส่ Thread Class แทรกไปที่ใดๆของ Unit ที่มีอยู่แล้ว
ตัวอย่างแรกผู้เขียนจะยกตัวอย่างโดยอ้างอิงกับฐานข้อมูลพนักงาน ผู้เขียนใช้ Component ADO ในการเรียกข้อมูลขึ้นมา โดยที่ขั้นตอนการเรียกข้อมูลจะข้ามไป ไม่ขอกล่่าวถึง
ผู้เขียนทดสอบเรียกข้อมูลบาง Field ของ Table Employee มาแสดง

ข้อมูลพนักงานเลือกใช้ 6 Field
 จากนั้นจะใช้ Thread นำข้อมูลพนักงาน แสดงผ่าน ListBox ให้เป็นข้อความที่สมบูรณ์ยิ่งขึ้น
 การสร้าง Thread Object ทำได้โดยเข้าไปที่เมนู File > New > Other


จะปรากฎ Dialog New item ให้เลือก Thread Object ที่อยู่ภายใน Tab New แล้วกด OK


โปรแกรมจะถามถึงชื่อ Thread Class ที่ต้องการสร้างขึ้น พร้อมกับชื่อของ Thread โดยชื่อของ Thread จะตั้งหรือไม่ก็ได้ โดยหาก Thread ไม่มีชื่อก็ใช้การอ้างอิงเป็น Thread id ก็ได้


หลังจากนั้นจะได้ ไฟล์ Unit ของ Thread Class ที่จะกอบด้วย Class และ Interface ของ Class รอให้นำไป Implement ต่อได้

TthdEmployee = class(TThread) //คลาสที่สืบทอดมาจาก TThread ที่อยู่ใน Libraly Classes
private
       procedure SetName;
protected
       procedure Execute; override;
end;


method ที่ได้สร้างมาให้แล้วคือ SetName ซึ่งมีหน้าที่กำหนด Thread info เมื่อมีการทำงานของ Thread ส่วน method Execute นั้นเป็น abstract method ที่ได้รับมาจากการสืบทอดมาจาก TThread เพื่อให้ implement ต่อ method Execute นี้สำคัญมาก เพราะ Thread จะใช้ method นี้ในการทำงาน

เมื่อผู้เขียนจะใช้งาน Thread ร่วมกับ DataSet และ ListBox ที่อยู่บนฟอร์ũ7; จึงจำเป็นต้องให้ Thread Class $26;ามารถใช้งาน DataSet ที่อยู่ภายนอก Class ได้ โดยผู้เขียนจะทำการ Hiding Constructor ของ Thread Class ตามนี้

TthdEmployee = class(TThread)
  private
       procedure SetName;
  protected
       procedure Execute; override;
  public
//เพิ่มส่วนของ Encapsulation สาธารณะ
       employeeDataSource: TDataSource; //ประกาศ Instance DataSource ที่รับมาจากภายนอก 
       employeeList: TListBox;  //ประกาศ Instance ListBox ที่รับมาจากภายนอก 


       // ประกาศ Interface เป็น Hiding Constructor
       constructor Create(DS:TDataSource;listBox: TListBox); 

end;


 จากนั้นก็เป็นส่วนของตัว method ที่ผู้เขียนจะทำการ Hiding เพื่อเพิ่มประสิทธิภาพของ Constructor เดิม

constructor TthdEmployee.Create(DS:TDataSource;listBox: TListBox);
begin 
      inherited Create(false); //อ้างใช้ Constructor เดิมของ TThread

      //อ้างอิง DataSource ที่รับมาจากภายนอกให้กับ Instance ที่ประกาศไว้  
      Self.employeeDataSource:=DS; 

      //  อ้างอิง ListBox ที่รับมาจากภายนอกให้กับ Instance ที่ประกาศไว้    
      Self.employeeList:=listBox;
end;
ต่อมาก็คือส่วนของกระบวนการทำงานของ Thread คือ method Execute คำสั่งที่อยู่ภายใน method หมายถึงระยะเวลาของ Thread ด้วย
ในตัวอย่างผู้เขียนจะทำการวนลูป นำข้อมูลที่อยู่ภายใน DataSet ผ่าน DataSource ที่รับเข้ามา เพื่อนำไปใส่ใน ListBox

procedure TthdEmployee.Execute;
Var
        itemText: String;
begin
        SetName; //เซ็ตค่า Thread info
        with Self.employeeDataSource.DataSet do
        Begin
               //ให้ curser อยู่ที่ตำแหน่งข้อมูลแถวแรกก่อน  
               First; 
               //ล้างค่าของ ListBox
               Self.employeeList.Clear;

               //วนลูปข้อมูลทั้งหมดที่มีอยู่ใน DataSet
               while not Eof do
               Begin

                    itemText:='รหัสพนักงาน '+FieldByName('EMP_COD').AsString;
                    itemText:=itemText + ' ชื่อ '+ FieldByName('EMP_NAM1').AsString + FieldByName('EMP_NAM2').AsString;
                    itemText:=itemText + ' นามสกุล '+ FieldByName('EMP_NAM3').AsString;

                    //แปลงค่า M หรือ F ของเพศเป็นข้อความ
                    if Trim(FieldByName('EMP_SEX').AsString)='M' then
                        itemText:=itemText + 'เป็น ผู้ชาย '
                    else
                        itemText:=itemText + 'เป็น ผู้หญิง ';

                    //เมื่อได้ข้อความทั้งหมดแล้วให้ใส่เข้าใน ListBox
                    Self.employeeList.Items.Add(itemText);

                    //เลื่อนไปแถวถัดไป
                    Next;
               end;
          end;
end;

ตอนนี้ Thread Class พร้อมที่จะทำงานแล้ว
หมายเหตุ
ไฟล์ของ Thread Class ที่สร้างขึ้นจำเป็นต้อง uses Libraly ของ Delphi เข้ามาให้ครบด้วย
 uses
    Classes {$IFDEF MSWINDOWS} , Windows {$ENDIF},DB,StdCtrls,SysUtils;


ผู้เขียนจะเรียกใช้งานจากฟอร์ม โดยเรียกที่ Event FormShow()

uses Unit2; //uses ไฟล์ของ Thread Class เข้ามา

procedure TForm1.FormShow(Sender: TObject);
begin
          //เมื่อเรียกใช้ Constructor ของ Thread ก็จะเริ่มทำงานทันที จนจบการทำงานของ method Execute
          TthdEmployee.Create(Self.DataSource1,Self.ListBox1);
end;

ทดสอบโดยรันโปรแกรมจะได้ผลตามที่ Thread ทำงาน


Source Code ทั้งหมด
ไฟล์ Unit1.pas หรือ Form1
unit Unit1;


interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, DB, ADODB;

type
   TForm1 = class(TForm)
     ADOConnection1: TADOConnection;
     ADOTable1: TADOTable;
     DataSource1: TDataSource;
     ADOTable1EMP_COD: TStringField;
     ADOTable1EMP_NAM1: TStringField;
     ADOTable1EMP_NAM2: TStringField;
     ADOTable1EMP_NAM3: TStringField;
     ADOTable1EMP_SEX: TStringField;
     ADOTable1EMP_BRTH: TDateTimeField;
     ListBox1: TListBox;
     procedure FormShow(Sender: TObject);
   private
     { Private declarations }
   public
     { Public declarations }
   end;

var
   Form1: TForm1;

implementation

uses Unit2;
{$R *.dfm}

procedure TForm1.FormShow(Sender: TObject);
begin
       TthdEmployee.Create(Self.DataSource1,Self.ListBox1);
end;


Unit2.pas ไฟล์ Unit ของ Thread
unit Unit2;

interface

uses
Classes
{$IFDEF MSWINDOWS} , Windows {$ENDIF},DB,StdCtrls,SysUtils;

type
    TthdEmployee = class(TThread)
    private
       procedure SetName;
    protected
       procedure Execute; override;
    public
       employeeDataSource: TDataSource;
       employeeList: TListBox;
       constructor Create(DS:TDataSource;listBox: TListBox);
   
end;

implementation
{ Important: Methods and properties of objects in visual components can only be
used in a method called using Synchronize, for example,

Synchronize(UpdateCaption);

and UpdateCaption could look like,

procedure TthdEmployee.UpdateCaption;
begin
       Form1.Caption := 'Updated in a thread';
end; }

{$IFDEF MSWINDOWS}
type
       TThreadNameInfo = record
       FType: LongWord; // must be 0x1000
       FName: PChar; // pointer to name (in user address space)
       FThreadID: LongWord; // thread ID (-1 indicates caller thread)
       FFlags: LongWord; // reserved for future use, must be zero
end;
{$ENDIF}

{ TthdEmployee }


procedure TthdEmployee.SetName;
{$IFDEF MSWINDOWS}
var
      ThreadNameInfo: TThreadNameInfo;
{$ENDIF}
begin
     
{$IFDEF MSWINDOWS}
       ThreadNameInfo.FType := $1000;
       ThreadNameInfo.FName := 'thdEmployee';
       ThreadNameInfo.FThreadID := $FFFFFFFF;
       ThreadNameInfo.FFlags := 0;

try
        RaiseException( $406D1388, 0, sizeof(ThreadNameInfo) div sizeof(LongWord), @ThreadNameInfo );
except
end;
       
{$ENDIF}
end;

procedure TthdEmployee.Execute;
Var
       itemText: String;
begin
       SetName;
       with Self.employeeDataSource.DataSet do
       Begin
           First;
           Self.employeeList.Clear;
           while not Eof do
           Begin
               itemText:=' รหัสพนักงาน '+FieldByName('EMP_COD').AsString;
               itemText:=itemText + ' ชื่อ '+ FieldByName('EMP_NAM1').AsString + FieldByName('EMP_NAM2').AsString;
               itemText:=itemText + ' นามสกุล '+ FieldByName('EMP_NAM3').AsString;
               if Trim(FieldByName('EMP_SEX').AsString)='M' then
                   itemText:=itemText + '  เป็น ผู้ชาย '
               else
                   itemText:=itemText + ' เป็น ผู้หญิง ';
               Self.employeeList.Items.Add(itemText);
               Next;
           end;
       end;
end;

constructor TthdEmployee.Create(DS:TDataSource;listBox: TListBox);
begin
            inherited Create(false);
            Self.employeeDataSource:=DS;
            Self.employeeList:=listBox;
end;

end.

วันพฤหัสบดีที่ 19 พฤษภาคม พ.ศ. 2554

.Net กับ Transactions PART I

Web Service กับ Transaction

คุณสมบัติ Transaction ที่ใช้ใน Web Service มีอยู่ 5 คุณสมบัติ คือ

  • Disabled:   หากมีการเรียกใช้งานจะไม่ใช้ transaction
  • NotSupported:    หากมีการเรียกใช้งานจะไม่รองรับ transaction  
  • Required: หากมี transaction หลักอยู่แล้วให้ใช้ตัวเดิม แต่ถ้ายังไม่มีก็จะสร้างใหม่
  • RequiresNew: หากมีการเรียกใช้งาน สร้าง transaction ใหม่เสมอ โดยไม่ต้องคำนึงถึงการทำงานอื่น
  • Supported:  ให้ใช้ transaction ร่วมกันโดยไม่สร้างใหม่  
มีรูปแบบการระบุให้ใช้ Transaction ตามภาพ
เมื่อมีการระบุ RequireหNew เมื่อมีการใช้ Transaction ใน web method ให้เป็น Transaction ใหม่เสมอ
 ADO.NET DataSet(.xsd) กับ Transaction

ส่วนใหญ่ Project ที่ผู้เขียนสร้างขึ้นในการทำงาน ถ้าเขียนบน .Net ก็มักจะใช้ในส่วนของ DataSet .xsd นั่นก็คือ DataSet ที่สร้างเพื่อทำงานกับฐานข้อมูล เมื่อเรียกใช้งานจาก Visual Studio (2008/2010) โปรแกรมก็จะสร้างหน้าจอ Design โครงสร้างของ DataSet ให้ใช้งานโดยสะดวก ซึ่งก็คือการช่วยเราสร้าง Class ที่ Inheritance มาจาก System.Data.DataSet นั่นเอง แต่ผู้เขียนจะข้ามขั้นตอนนี้ไป

โครงสร้างภายในไฟล์ DataSet บรรจุ TableAdapter อยู่หลายตัว

การใช้งาน DataSet .xsd ไม่ยาก และยังประหยัดเวลาการเขียน SQL Statement อีกด้วย แต่ปัญหาก็คือเรื่องของการ Manage Transaction ซึ่งไม่สามารถระบุจุดที่เปิด Transaction ที่ on runtime code ของเราได้ เนื่องจากการสร้าง Object Instance ขึ้นมาใช้งานแทนตัวของ DataSet ทำให้การทำงานของ Transaction เป็นกลไกภายในของ Object

การแก้ปัญหาคือ การนำ TransactionScope มาใช้

TransactionScope 
อยู่ภายใน System.Transactions เมื่อเรียกมาใช้งานจะมีรูปแบบการทำงานเป็นบล๊อก คือ จะควบคุมการใช้ Transaction ภายในบล๊อกของตัวเองเท่านั้น และจะทำการ Rollback Transaction อัตโนมัติเมื่อคำสั่งภายในบล๊อกทำงานไม่สำเร็จ 

using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
{
         //คำสั่งการทำงานกับฐานข้อมูล

         scope.Complete(); //หากการทำงานไม่สำเร็จถึงบรรทัดนี้ จะทำการ Rollback ทันที
}

ลองประยุคใช้งานจริงกับตัวอย่างข้างต้น


ค่าของ TransactionScopeOption จะสามารถระบุได้ 3 ค่า คือ
  • Required : หากไม่มี Transaction อื่นอยู่ให้สร้างขึ้นใหม่ หากมีอยู่แล้วให้ใช้ Transaction เดิม
  • Requires New : ไม่สนใจว่ามี Transaction อื่นใดอยู่หรือไม่ ให้สร้างใหม่เสมอ 
  • Suppress : ไม่สร้าง Transaction 

หมายเหตุ
การใช้ TransactionScope จำเป็นต้องตั้งค่า MSDTC Service ให้ถูกต้องด้วย

การตั้งค่า MSDTC Service (Windows XP,Server 2003)  
http://www.codeproject.com/KB/cs/MSDTC_And_TransactionScop.aspx

การตั้งค่า MSDTC Service (Windows 7)    
http://www.thereforesystems.com/turn-on-msdtc-windows-7/