Most popular

วันเสาร์ที่ 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.

1 ความคิดเห็น:

  1. อยากให้เขียนตัวอย่าง ใช้ thread ในdelphi 7 แบบแทรกใน unit เดียวกับฟอร์ม อ่านข้อมูลจากฐานข้อมูลมาใส่ใน combobox ครับ (ปกติที่ผมใช้อยู่ใช้ Tquery อ่านมาใส่ commbobox บางทีข้อมูลเยอะ ๆ จะกระตุกนิด ๆ ครับ)

    ขอบคุณครับ

    ตอบลบ