当前位置:编程学习 > Delphi >>

Delphi Thread

TThread 详解
        我们常有工作线程和主线程之分,工作线程负责作一些后台操作,比如接收邮件;

                                                              主线程负责界面上的一些显示。

工作线程的好处在某些时候是不言而喻的,你的主界面可以响应任何操作,而背后的线程却在默默地工作。


         VCL中,工作线程执行在Execute方法中,你必须从TThread继承一个类并覆盖Execute方法,在这个方法中,所有代码都是在另一个 线程中执行的,除此之外,你的线程类的其他方法都在主线程执行,包括构造方法,析构方法,Resume等,很多人常常忽略了这一点。

最简单的一个线程类如下:

TMyThread = class(TThread)
protected
procedure Execute; override;
end;

在Execute中的代码,有一个技术要点,如果你的代码执行时间很短,像这样,Sleep(1000),那没有关系;如果是这样Sleep (10000),10秒,那么你就不能直接这样写了,须把这10秒拆分成10个1秒,然后判断Terminated属性,像下面这样:

procedure TMyThread.Execute;
var
   i: Integer;
begin
   for i := 0 to 9 do
      if not Terminated then
        Sleep(1000)
     else
        Break;
end;

这样写有什么好处呢,

      想想你要关闭程序,在关闭的时候调用MyThread.Free,这个时候线程并没有马上结束,它调用WaitFor,等待 Execute执行完后才能释放。

       你的程序就必须等10秒以后才能关闭,受得了吗。如果像上面那样写,在程序关闭时,调用Free之后,它顶多再等一秒就 会关闭。

        为什么?答案得去线程类的Destroy中找,它会先调用Terminate方法,在这个方法里面它把Terminated设为True(仅此而 已,很多人以为是结束线程,其实不是)。

      请记住这一切是在主线程中操作的,所以和Execute是并行执行的。既然Terminated属性已为 Ture,那么在Execute中判断之后,当然就Break了,Execute执行完毕,线程类也正常释放。

或者有人说,TThread可以设FreeOnTerminate属性为True,线程类就能自动释放。除非你的线程执行的任务很简单,不然,还是不要去理会这个属性,一切由你来操作,才能使线程更灵活强大。

          接下来的问题是如何使工作线程和主线程很好的通信,很多时候主线程必须得到工作线程的通知,才能做出响应。比如接收邮件,工作线程向服务器收取邮件,收取完毕之后,它得通知主线程收到多少封邮件,主线程才能弹出一个窗口通知用户。

在VCL中,我们可以用两种方法,一种是向主线程中的窗体发送消息,另一种是使用异步事件。

第一种方法其实没有第二种来得方便。想想线程类中的OnTerminate事件,这个事件由线程函数的堆栈引起,却在主线程执行。

事实上,真正的线程函数是这个:
function ThreadProc(Thread: TThread): Integer;

函数里面有Thread.Execute,这就是为什么Execute是在其他线程中执行,该方法执行之后,有如下句:
Thread.DoTerminate;

而线程类的DoTerminate方法里面是
if Assigned(FOnTerminate) then Synchronize(CallOnTerminate);


显然Synchronize方法使得CallOnTerminate在主线程中执行,而CallOnTerminate里面的代码其实就是:
if Assigned(FOnTerminate) then FOnTerminate(Self);

只要Execute方法一执行完就发生OnTerminate事件。不过有一点是必须注意,OnTerminate事件发生后,线程类不一定会释 放,只有在FreeOnTerminate为True之后,才会Thread.Free。看一下ThreadProc函数就知道。

依照Onterminate事件,我们可以设计自己的异步事件。

Synchronize方法只能传进一个无参数的方法类型,但我们的事件经常是要带一些参数的,这个稍加思考就可以得到解决,即在线程类中保存参数,触发事件前先设置参数,再调用异步事件,参数复杂的可以用记录或者类来实现。

假设这样,上面的代码每睡一秒,线程即向外面引发一次事件,我们的类可以这样设计:

 

[delphi] view plaincopyprint?TSecondEvent = procedure (Second: Integer) of object; 
TMyThread = class(TThread) 
private 
FSecond: Integer; 
FSecondEvent: TSecondEvent; 
procedure CallSecondEvent; 
protected 
procedure Execute; override; 
public 
property SencondEvent: TSecondEvent read FSecondEvent 
write FSecondEvent; 
end; 
 
{ TMyThread } 
 
procedure TMyThread.CallSecondEvent; 
begin 
if Assigned(FSecondEvent) then 
FSecondEvent(FSecond); 
end; 
 
procedure TMyThread.Execute; 
var 
i: Integer; 
begin 
for i := 0 to 9 do 
if not Terminated then 
begin 
Sleep(1000); 
FSecond := i; 
Synchronize(CallSecondEvent); 
end 
else 
Break; 
end;  
在主窗体中假设我们这样操作线程: 
 
procedure TForm1.Button1Click(Sender: TObject); 
begin 
MyThread := TMyThread.Create(true); 
MyThread.OnTerminate := ThreadTerminate; 
MyThread.SencondEvent := SecondEvent; 
MyThread.Resume; 
end; 
 
procedure TForm1.ThreadTerminate(Sender: TObject); 
begin 
ShowMessage('ok'); 
end; 
 
procedure TForm1.SecondEvent(Second: Integer); 
begin 
Edit1.Text := IntToStr(Second); 
end; 
 
我们将每隔一秒就得到一次通知并在Edit中显示出来。 
 
现在我们已经知道如何正确使用Execute方法,以及如何在主线程与工作线程之间通信了。但问题还没有结束,有一种情况出乎我的意料之外,即如果 线程中有一些资源,Execute正在使用这些资源,而主线程要释放这个线程,这个线程在释放的过程中会释放掉资源。想想会不会有问题呢,两个线程,一个 在使用资源,一个在释放资源,会出现什么情况呢,  
 
用下面代码来说明: 
 
type 
TMyClass = class 
private 
FSecond: Integer; 
public 
procedure SleepOneSecond; 
end; 
 
TMyThread = class(TThread) 
private 
FMyClass: TMyClass; 
protected 
procedure Execute; override; 
public 
constructor MyCreate(CreateSuspended: Boolean); 
destructor Destroy; override; 
end; 
 
implementation 
 
{ TMyThread } 
 
constructor TMyThread.MyCreate(CreateSuspended: Boolean); 
begin 
inherited Create(CreateSuspended); 
FMyClass := TMyClass.Create; 
end; 
 
destructor TMyThread.Destroy; 
begin 
FMyClass.Free; 
FMyClass := nil; 
inherited; 
end; 
 
procedure TMyThread.Execute; 
var 
i: Integer; 
begin 
for i := 0 to 9 do 
FMyClass.SleepOneSecond; 
end; 
 
{ TMyClass } 
 
procedure TMyClass.SleepOneSecond; 
begin 
FSecond := 0; 
Sleep(1000); 
end; 
 
end.  
 
用下面的代码来调用上面的类: 
 
procedure TForm1.Button1Click(Sender: TObject); 
begin 
MyThread := TMyThread.MyCreate(true); 
MyThread.OnTerminate := ThreadTerminate; 
MyThread.Resume; 
end; 
 
procedure TForm1.Button2Click(Sender: TObject); 
begin 
MyThre

补充:软件开发 , Delphi ,
CopyRight © 2022 站长资源库 编程知识问答 zzzyk.com All Rights Reserved
部分文章来自网络,