delphi 线程教学第三节:设计一个有生命力的工作线程
第三节:设计一个有生命力的工作线程
创建一个线程,用完即扔。相信很多初学者都曾这样使用过。
频繁创建释放线程,会浪费大量资源的,不科学。
1.如何让多线程能多次被复用?
关键是不让代码退出 Execute 这个函数,一旦退出此函数,此线程的生命周期即结束。
要做到这一点,就需要在 Execute 中写一个”死循环“。大致如下:
| procedureTFooThread.Execute;begin  // 0.挂起  whilenotTerminated do// Terminated 是 TThread 的一个 Boolean 属性。  begin    // 1.获得参数    // 2.计算    // 3.返回结果    // 4.挂起  end;end; | 
原本 TThread 是有挂起功能这个函数的,叫 suspend,但是在 XE2 后,已经废止此函数。
故需要找一个替代品 TEvent ,此类在 System.SyncObjs 单元中。于是:
unit uFooThread;interfaceuses  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;implementationconstructor 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; // 用于计算的线程类interfaceuses  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;implementationconstructor 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; //在窗口中调用interfaceuses  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.至此,一个完整的可复用的线程已基本完成。下一节讲解,如何将此线程设计得更为通用和严谨。
 
                    
                     
                    
                 
                    
                 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号 
