多執行緒編程(1) - 先入門

image

先編寫一個循環 50000 次的程式,每次在程式介面左上方(10,10)的位置輸出數字,程式碼如下:

  1 procedure TForm1.btn2Click(Sender: TObject);
  2 var
  3   i: Integer;
  4 begin
  5   for i := 0 to 500000 do
  6   begin
  7     Canvas.TextOut(10, 10, IntToStr(i));
  8   end;
  9 end;

上面程式運行時, 在程式運行期間拖動窗體,窗體基本是 卡"死" 的。

解決卡死方法1(Application.ProcessMessages)

一個簡單的辦法( Application.ProcessMessages )來解決這個問題,程式碼如下:

  1 procedure TForm1.btn2Click(Sender: TObject);
  2 var
  3   i: Integer;
  4 begin
  5   for i := 0 to 500000 do
  6   begin
  7     Canvas.TextOut(10, 10, IntToStr(i));
  8     Application.ProcessMessages; // 解決方法之一
  9   end;
 10 end;

這個 Application.ProcessMessages  它會檢查並先處理消息隊列中的其他消息.
但這算不上多執行緒, 運行中拖動窗體, 你會發現循環會暫停下來...

在 Delphi 中使用多執行緒有兩種方法:

  1. 調用 API
  2. 使用 TThread 類

解決卡死方法2(調用 API)

  1 function MyTest(p: Pointer): Integer; stdcall;
  2 var
  3   i: Integer;
  4 begin
  5   for i := 0 to 500000 do
  6   begin
  7     Form1.Canvas.Lock; // 在 Canvas 中 Lock ,其他訪問先暫停
  8     Form1.Canvas.TextOut(10, 10, IntToStr(i));
  9     Form1.Canvas.Unlock; // 用完了,解鎖
 10   end;
 11   Result := 0;
 12 end;
 13 
 14 procedure TForm1.btn3Click(Sender: TObject);
 15 var
 16   ID: Cardinal;
 17 begin
 18   CreateThread(nil, 0, @MyTest, nil, 0, ID);
 19 end;

CreateThread 要使用的函數是系統級的,不能是某個類的方法,必須有嚴格的格式(參數、返回值)要求,還必須用上 stdcall 是協調參數順序的,雖然這裡只有一個參數沒有順序可言,但這是使用系統函數的慣例。

CreateThread 還需要一個 var 參數來接收新建執行緒的 ID,在 Delphi 10.3.3 中為 Cardinal 類型。

解決卡死方法3(使用 TThread 類)

TThread 類有一個抽象方法(Execute),抽象類只能被繼承使用,下面繼承為 TMyThread,主要是實現抽象方法 Execute,等我們實例化 TMyThread 後,首先會執行 Execute 方法中的程式碼。

  1 type
  2   TMyThread = class(TThread)
  3   protected
  4     procedure Execute; override;
  5   end;
  6 
  7 implementation
  8 
  9 {$R *.dfm}
 10 { TMyThread }
 11 
 12 procedure TMyThread.Execute;
 13 var
 14   i: Integer;
 15 begin
 16   inherited;
 17   FreeOnTerminate := True;
 18   for i := 0 to 500000 do
 19   begin
 20     Form1.Canvas.Lock;
 21     Form1.Canvas.TextOut(10, 10, IntToStr(i));
 22     Form1.Canvas.Unlock;
 23   end;
 24 end;
 25 
 26 procedure TForm1.btn1Click(Sender: TObject);
 27 var
 28   MyThread: TMyThread;
 29 begin
 30   MyThread := TMyThread.Create(True);
 31   MyThread.Resume;
 32 
 33   //TMyThread.Create(False); 也可以這樣實例化
 34 
 35   //with TMyThread.Create(True) do Resume; 還可以這樣實例化
 36 end;

在上面程式碼中,實例化用到的 MyThread 變數,毫無用處,可以直接

 TMyThread.Create(False);

執行或者

with TMyThread.Create(True) do Resume;

執行緒建立後不會立即調用 Execute,可以在需要的時候用 Resume 方法執行執行緒。

在 TThread 類的例子中,還有一個這樣的句子:

FreeOnTerminate := True;

由於 TThread 的特殊性,很多時候我們不能確定執行緒什麼時候執行完畢,因此不能 Free,如果 FreeOnTerminate 為 True,執行緒執行完畢後自動釋放。