delphi 線程教學第三節:設計一個有生命力的工作線程


第三節:設計一個有生命力的工作線程
 
創建一個線程,用完即扔。相信很多初學者都曾這樣使用過。
頻繁創建釋放線程,會浪費大量資源的,不科學。
 
1.如何讓多線程能多次被復用?
 
關鍵是不讓代碼退出 Execute 這個函數,一旦退出此函數,此線程的生命周期即結束。
要做到這一點,就需要在 Execute 中寫一個”死循環“。大致如下:
 
procedure  TFooThread . Execute;
begin
   // 0.掛起
   while  not  Terminated  do  // Terminated 是 TThread 的一個 Boolean 屬性。
   begin
     // 1.獲得參數
     // 2.計算
     // 3.返回結果
     // 4.掛起
   end ;
end ;
 
原本 TThread 是有掛起功能這個函數的,叫 suspend,但是在 XE2 后,已經廢止此函數。
故需要找一個替代品 TEvent ,此類在 System.SyncObjs 單元中。於是:
 
unit  uFooThread;
interface
uses
   System . Classes, System . SyncObjs;
type
 
   TFooThread =  class (TThread)
   private
     FEvent: TEvent;  // 此類用來實現線程掛起功能
   protected
     procedure  Execute; override;
   public
     constructor  Create(CreateSuspended:  Boolean );
     destructor  Destroy; override;
     procedure  StartThread;  // 設計線程的啟動函數。
   end ;
 
implementation
 
constructor  TFooThread . Create(CreateSuspended:  Boolean );
begin
   inherited ;
   FEvent := TEvent . Create( nil true false '' ); // 默認讓 FEvent 無信號
   FreeOnTerminate :=  true // True的意義是,代碼退出 Execute 后,本類自動釋放。
end ;
 
destructor  TFooThread . Destroy;
begin
   FEvent . Free;
   inherited ;
end ;
 
procedure  TFooThread . Execute;
begin
   FEvent . WaitFor;
   // 如果 FEvent 無信號,就一直等。
   // 如果 FEvent 已被設置有信號,就退出 WaitFor ,解除阻塞。
   // 這樣,就實現了線程的掛起功能。
   // 掛起是指線程時空的代碼,停在了當前位置,就是 FEvent.WaitFor 這個位置。
   FEvent . ResetEvent;  // 清除信號,以便下一次繼續掛起。
   while  not  Terminated  do
   begin
     // 1.獲得參數
     // 2.計算
     // 3.返回結果
     FEvent . WaitFor;  // 同上
     FEvent . ResetEvent;
   end ;
end ;
 
procedure  TFooThread . StartThread;
begin
   FEvent . SetEvent;
   // 所謂啟動線程功能,就是要讓 FEvent 有信號,讓它解除阻塞。
end ;
end .
以上代碼已實現一個有生命力的線程。
 
2. 如何正常退出線程?
 
必須正視這個問題,線程代碼必須要有正常的退出方式,切不可用 KillThread 等暴力方法。
 
// 線程正常退出示例
var
   foo: TFooThread;
begin
   foo := TFooThread . Create( false );  // false 是指創建后不掛起,直接運行 Execute 中的代碼。
   sleep( 1000 );  // 技術性代碼,請忽略,但此處又不可少。
   foo . Terminate;  // 此句的功能是 Terminated:=True;
   // Terminated TThread 的一個 Boolean 屬性
   // 在 Execute 函數中我們用它做為退出循環的標志
   // 請學習系統源碼中的英語命名的方法,注意詞性,時態。
   // Terminate 是動詞,是一個函數。而 Terminated 是過去分詞,是一個屬性。
   foo . StartThread;  // 啟動線程。
   // FreeOnTerminated 已在 Create 函數中設置為 True 。
   // 所以,代碼退出 Execute 后,foo 會自動 free 的。
end ;
 
3.線程復用示例
 
unit  uFooThread; // 用於計算的線程類
interface
uses
   System . Classes, System . SyncObjs;
 
type
   TFooThread =  class ;
   TOnWorked =  procedure (Sender: TFooThread)  of  object ;
   TFooThread =  class (TThread)
   private
     FEvent: TEvent;
   protected
     procedure  Execute; override;
   public
     constructor  Create(CreateSuspended:  Boolean );
     destructor  Destroy; override;
     procedure  StartThread;
   public
     Num:  integer ;
     Total:  integer ;
     OnWorked: TOnWorked;
   end ;
 
implementation
 
constructor  TFooThread . Create(CreateSuspended:  Boolean );
begin
   inherited ;
   FEvent := TEvent . Create( nil true false '' );
   FreeOnTerminate :=  true ;
end ;
 
destructor  TFooThread . Destroy;
begin
   FEvent . Free;
   inherited ;
end ;
 
procedure  TFooThread . Execute;
var
   i:  integer ;
begin
   FEvent . WaitFor;
   FEvent . ResetEvent;
   while  not  Terminated  do
   begin
     Total :=  0 ;
 
     if  Num >  0  then
     begin
       for  i :=  1  to  Num  do
       begin
         Total := Total + i;
         sleep( 10 ); //故意讓線程耗時,以達到更好的演示效果。
       end ;
     end ;
 
     if  Assigned(OnWorked)  then
       OnWorked(self);
 
     FEvent . WaitFor;
     FEvent . ResetEvent;
   end ;
end ;
 
procedure  TFooThread . StartThread;
begin
   FEvent . SetEvent;
end ;
 
end .
 
unit  Unit11; //在窗口中調用
interface
uses
   Winapi . Windows, Winapi . Messages, System . SysUtils, System . Variants, System . Classes, Graphics,
   Vcl . Controls, Vcl . Forms, Vcl . Dialogs, Vcl . StdCtrls, uFooThread;
 
type
   TForm11 =  class (TForm)
     Memo1: TMemo;
     Button1: TButton;
     Edit1: TEdit;
     procedure  FormCreate(Sender: TObject);
     procedure  Button1Click(Sender: TObject);
     procedure  FormDestroy(Sender: TObject);
   private
     { Private declarations }
     FooThread: TFooThread;
     procedure  OnWorked(Sender: TFooThread);  // 用來接收線程的 OnWorked 事件。
     // 取名是任意的,只要參數相同。
     // 如果你也可以取名為 procedure OnFininshed (O:TFooThread); 同樣有效。
   public
     { Public declarations }
   end ;
 
var
   Form11: TForm11;
 
implementation
{ $R  *.dfm}
// 本例為了照顧初學者,未對控件取正確的名字。
// 以后的章節中,將全部采用合理的命名,且提供源碼下載地址。
procedure  TForm11 . Button1Click(Sender: TObject);
var
   n:  integer ;
begin
   Button1 . Enabled :=  false // 禁用此 button ,以防線程運行期間誤點
   // 這是很重要的!用了線程,就要對所有的情況負責。
   // button 被禁用后,在線程計算完成的事件中,將恢復
   // 就可以繼續點擊它了。
   n := StrToIntDef(Edit1 . Text,  0 );
   FooThread . Num := n;
   FooThread . StartThread;
end ;
 
procedure  TForm11 . FormCreate(Sender: TObject);
begin
   FooThread := TFooThread . Create( false );
   FooThread . OnWorked := self . OnWorked;
   // 如果按另一個定義的名字也可以寫:
   // FooThread.OnWorked:=self.OnFininished;
end ;
 
procedure  TForm11 . FormDestroy(Sender: TObject);
begin
   FooThread . Terminate;
   FooThread . StartThread;
   // 釋放線程。此處不是很嚴謹,以后章節的代碼中將完善它。
end ;
 
procedure  TForm11 . OnWorked(Sender: TFooThread);
var
   s:  string ;
begin
   s := IntToStr(Sender . Num);
   s := s +  '的累加和為:' ;
   s := s + IntToStr(Sender . Total);
   // 此處是線程時空,操作 UI 要用 Synchronize;
   TThread . Synchronize( nil ,
     procedure
     begin
       Memo1 . Lines . Add(s); //匿名函數,可以用外部的變量(s)。這是很高級的特性。
       Button1 . Enabled :=  true //恢復按鈕,以便繼續使用。
     end );
   // 此處還可以作為繼續啟動線程的入口,下一章節會講解。
end ;
 
end .
 
至此,一個完整的可復用的線程已基本完成。下一節講解,如何將此線程設計得更為通用和嚴謹。
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM