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