delphi 线程教学第三节:设计一个有生命力的工作线程

第三节:设计一个有生命力的工作线程

创建一个线程,用完即扔。相信很多初学者都曾这样使用过。

频繁创建释放线程,会浪费大量资源的,不科学。

1.如何让多线程能多次被复用?

关键是不让代码退出 Execute 这个函数,一旦退出此函数,此线程的生命周期即结束。

要做到这一点,就需要在 Execute 中写一个”死循环“。大致如下:

procedureTFooThread.Execute;

begin

// 0.挂起

whilenotTerminateddo// Terminated 是 TThread 的一个 Boolean 属性。

begin

// 1.获得参数

// 2.计算

// 3.返回结果

// 4.挂起

end;

end;

原本 TThread 是有挂起功能这个函数的,叫 suspend,但是在 XE2 后,已经废止此函数。

故需要找一个替代品 TEvent ,此类在 System.SyncObjs 单元中。于是:

unituFooThread;

interface

uses

System.Classes, System.SyncObjs;

type

TFooThread =class(TThread)

private

FEvent: TEvent;// 此类用来实现线程挂起功能

protected

procedureExecute; override;

public

constructorCreate(CreateSuspended:Boolean);

destructorDestroy; override;

procedureStartThread;// 设计线程的启动函数。

end;

implementation

constructorTFooThread.Create(CreateSuspended:Boolean);

begin

inherited;

FEvent := TEvent.Create(nil,true,false,'');// 默认让 FEvent 无信号

FreeOnTerminate :=true;// True的意义是,代码退出 Execute 后,本类自动释放。

end;

destructorTFooThread.Destroy;

begin

FEvent.Free;

inherited;

end;

procedureTFooThread.Execute;

begin

FEvent.WaitFor;

// 如果 FEvent 无信号,就一直等。

// 如果 FEvent 已被设置有信号,就退出 WaitFor ,解除阻塞。

// 这样,就实现了线程的挂起功能。

// 挂起是指线程时空的代码,停在了当前位置,就是 FEvent.WaitFor 这个位置。

FEvent.ResetEvent;// 清除信号,以便下一次继续挂起。

whilenotTerminateddo

begin

// 1.获得参数

// 2.计算

// 3.返回结果

FEvent.WaitFor;// 同上

FEvent.ResetEvent;

end;

end;

procedureTFooThread.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.线程复用示例

unituFooThread; // 用于计算的线程类

interface

uses

System.Classes, System.SyncObjs;

type

TFooThread =class;

TOnWorked =procedure(Sender: TFooThread)ofobject;

TFooThread =class(TThread)

private

FEvent: TEvent;

protected

procedureExecute; override;

public

constructorCreate(CreateSuspended:Boolean);

destructorDestroy; override;

procedureStartThread;

public

Num:integer;

Total:integer;

OnWorked: TOnWorked;

end;

implementation

constructorTFooThread.Create(CreateSuspended:Boolean);

begin

inherited;

FEvent := TEvent.Create(nil,true,false,'');

FreeOnTerminate :=true;

end;

destructorTFooThread.Destroy;

begin

FEvent.Free;

inherited;

end;

procedureTFooThread.Execute;

var

i:integer;

begin

FEvent.WaitFor;

FEvent.ResetEvent;

whilenotTerminateddo

begin

Total :=0;

ifNum >0then

begin

fori :=1toNumdo

begin

Total := Total + i;

sleep(10);//故意让线程耗时,以达到更好的演示效果。

end;

end;

ifAssigned(OnWorked)then

OnWorked(self);

FEvent.WaitFor;

FEvent.ResetEvent;

end;

end;

procedureTFooThread.StartThread;

begin

FEvent.SetEvent;

end;

end.

unitUnit11; //在窗口中调用

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;

procedureFormCreate(Sender: TObject);

procedureButton1Click(Sender: TObject);

procedureFormDestroy(Sender: TObject);

private

{ Private declarations }

FooThread: TFooThread;

procedureOnWorked(Sender: TFooThread);// 用来接收线程的 OnWorked 事件。

// 取名是任意的,只要参数相同。

// 如果你也可以取名为 procedure OnFininshed (O:TFooThread); 同样有效。

public

{ Public declarations }

end;

var

Form11: TForm11;

implementation

{$R*.dfm}

// 本例为了照顾初学者,未对控件取正确的名字。

// 以后的章节中,将全部采用合理的命名,且提供源码下载地址。

procedureTForm11.Button1Click(Sender: TObject);

var

n:integer;

begin

Button1.Enabled :=false;// 禁用此 button ,以防线程运行期间误点

// 这是很重要的!用了线程,就要对所有的情况负责。

// button 被禁用后,在线程计算完成的事件中,将恢复

// 就可以继续点击它了。

n := StrToIntDef(Edit1.Text,0);

FooThread.Num := n;

FooThread.StartThread;

end;

procedureTForm11.FormCreate(Sender: TObject);

begin

FooThread := TFooThread.Create(false);

FooThread.OnWorked := self.OnWorked;

// 如果按另一个定义的名字也可以写:

// FooThread.OnWorked:=self.OnFininished;

end;

procedureTForm11.FormDestroy(Sender: TObject);

begin

FooThread.Terminate;

FooThread.StartThread;

// 释放线程。此处不是很严谨,以后章节的代码中将完善它。

end;

procedureTForm11.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.

至此,一个完整的可复用的线程已基本完成。下一节讲解,如何将此线程设计得更为通用和严谨。