前言
由于项目的关系,所以和线程的接触颇多,常常遇到问题,常常看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方法只能传进一个无参数的方法类型,但我们的事件经常是要带一些参数的,这个稍加思考就可以得到解决,即在线程类中保存参数,触发事件前先设置参数,再调用异步事件,参数复杂的可以用记录或者类来实现。
假设这样,上面的代码每睡一秒,线程即向外面引发一次事件,我们的类可以这样设计:
TSecondEvent=procedure(Second:Integer)ofobject;
TMyThread=class(TThread)
private
FSecond:Integer;
FSecondEvent:TSecondEvent;
procedureCallSecondEvent;
protected
procedureExecute;override;
public
propertySencondEvent:TSecondEventreadFSecondEvent
writeFSecondEvent;
end;
{TMyThread}
procedureTMyThread.CallSecondEvent;
begin
ifAssigned(FSecondEvent)then
FSecondEvent(FSecond);
end;
procedureTMyThread.Execute;
var
i:Integer;
begin
fori:=0to9do
ifnotTerminatedthen
begin
Sleep(1000);
FSecond:=i;
Synchronize(CallSecondEvent);
end
else
Break;
end;
在主窗体中假设我们这样操作线程:
procedureTForm1.Button1Click(Sender:TObject);
begin
MyThread:=TMyThread.Create(true);
MyThread.OnTerminate:=ThreadTerminate;
MyThread.SencondEvent:=SecondEvent;
MyThread.Resume;
end;
procedureTForm1.ThreadTerminate(Sender:TObject);
begin
ShowMessage('ok');
end;
procedureTForm1.SecondEvent(Second:Integer);
begin
Edit1.Text:=IntToStr(Second);
end;
我们将每隔一秒就得到一次通知并在Edit中显示出来。
现在我们已经知道如何正确使用Execute方法,以及如何在主线程与工作线程之间通信了。但问题还没有结束,有一种情况出乎我的意料之外,即如果线程中有一些资源,Execute正在使用这些资源,而主线程要释放这个线程,这个线程在释放的过程中会释放掉资源。想想会不会有问题呢,两个线程,一个在使用资源,一个在释放资源,会出现什么情况呢,
用下面代码来说明:
type
TMyClass=class
private
FSecond:Integer;
public
procedureSleepOneSecond;
end;
TMyThread=class(TThread)
private
FMyClass:TMyClass;
protected
procedureExecute;override;
public
constructorMyCreate(CreateSuspended:Boolean);
destructorDestroy;override;
end;
implementation
{TMyThread}
constructorTMyThread.MyCreate(CreateSuspended:Boolean);
begin
inheritedCreate(CreateSuspended);
FMyClass:=TMyClass.Create;
end;
destructorTMyThread.Destroy;
begin
FMyClass.Free;
FMyClass:=nil;
inherited;
end;
procedureTMyThread.Execute;
var
i:Integer;
begin
fori:=0to9do
FMyClass.SleepOneSecond;
end;
{TMyClass}
procedureTMyClass.SleepOneSecond;
begin
FSecond:=0;
Sleep(1000);
end;
end.
用下面的代码来调用上面的类:
procedureTForm1.Button1Click(Sender:TObject);
begin
MyThread:=TMyThread.MyCreate(true);
MyThread.OnTerminate:=ThreadTerminate;
MyThread.Resume;
end;
procedureTForm1.Button2Click(Sender:TObject);
begin
MyThread.Free;
end;
先点击Button1创建一个线程,再点击Button2释放该类,出现什么情况呢,违法访问,是的,MyThread.Free时,MyClass被释放掉了
FMyClass.Free;
FMyClass:=nil;
而此时Execute却还在执行,并且调用MyClass的方法,当然就出现违法访问。对于这种情况,有什么办法来防止呢,我想到一种方法,即在线程类中使用一个成员,假设为FFinished,在Execute方法中有如下的形式:
FFinished := False;
try
//... ...
finally
FFinished := True;
End;
接着在线程类的Destroy中有如下形式:
While not FFinished do
Sleep(100);
MyClass.Free;
这样便能保证MyClass能被正确释放。
线程是一种很有用的技术。但使用不当,常使人头痛。在CSDN论坛上看到一些人问,我的窗口在线程中调用为什么出错,主线程怎么向其他线程发送消息等等,其实,我们在抱怨线程难用时,也要想想我们使用的方法对不对,只要遵循一些正确的使用规则,线程其实很简单。
后记
上面有一处代码有些奇怪:FMyClass.Free; FMyClass:=nil;如果你只写FMyClass.Free,线程类还不会出现异常,即调用FMyClass.SleepOneSecond不会出错。我在主线程中试了下面的代码
MyClass := TMyClass.Create;
MyClass.SleepOneSecond;
MyClass.Free;
MyClass.SleepOneSecond;
同样也不会出错,但关闭程序时就出错了,如果是这样:
MyClass := TMyClass.Create;
MyClass.SleepOneSecond;
MyClass.Free;
MyThread := TMyThread.MyCreate(true);
MyThread.OnTerminate := ThreadTerminate;
MyThread.Resume;
MyClass.SleepOneSecond;
马上就出错。所以这个和线程类无线,应该是Delphi对于堆栈空间的释放规则,我想MyClass.Free之后,该对象在堆栈上空间还是保留着,只是允许其他资源使用这个空间,所以接着调用下面这一句MyClass.SleepOneSecond就不会出错,当程序退出时可能对堆栈作一些清理导致出错。而如果MyClass.Free之后即创建MyThread,大概MyClass的空间已经被MyThread使用,所以再调用MyClass.SleepOneSecond就出错了。
分享到:
相关推荐
并行计算是当今热门的一个技术,本文档简单介绍了多核多线程的入门知识,可以作为初学者入门的好材料。
主要介绍了Linux多核多线程编程相关知识,以及如何借助工具进行优化,利用多线程提高程序性能。源码实例丰富,是多核多线程中初学者必备文档。
线程杂谈!!!!!!!!!!!!
多线程编程指南 SUN公司 多线程编程值得研究的好书
为常见的高并发场景提供一些思路,从简单的分库分表到硬件级负载均衡都会涉及到,在不同的场景上选择不同的应对措施
(五)——传了值还是传了引用(六)——字符串(String)杂谈 (七)——日期和时间的处理 (八)——聊聊基本类型(内置类型)(九)——继承、多态、重载和重写(十)——话说多线程 (十一)——这些运算符你是否...
(一)类的初始化顺序 (二)到底创建了几个...(六)字符串(String)杂谈 (七)日期和时间的处理 (八)聊聊基本类型(内置类型) (九)继承、多态、重载和重写 (十)话说多线程 (十一)这些运算符你是否还记得?
来自网络,主要包括以下内容:1、类初始化的顺序;...6.String杂谈;7.日期与时间的处理;8.基本类型总结;9.继承,多态,重载,重写;10.多线程;11.运算符总结。 适合将要笔试面试Java的朋友参考。
1.6 JAVA面试题解惑系列(六)——字符串(String)杂谈 1.7 JAVA面试题解惑系列(七)——日期和时间的处理 1.8 JAVA面试题解惑系列(八)——聊聊基本类型(内置类型) 1.9 JAVA面试题解惑系列(九)——继承、...
上面的题目带有“黑客”两个字,请大家别误会了,其实没有多少是讲黑客的,这完全是一篇菜鸟级的编程杂谈,如果您已是高手,就不必在此浪费时间了 。前几天在网上看了“病毒”兄写的《WIN下编程须知》一文,觉得在...
类的初始化顺序 到底创建了几个String对象? 变量(属性)的覆盖 ...字符串(String)杂谈 日期和时间的处理 聊聊基本类型(内置类型) 继承、多态、重载和重写 话说多线程 这些运算符你是否还记得?
JDK 线程相关源码 框架使用 web 层框架 Spring MVC Webflux 持久层框架 Hibernate Mybatis 消息中间件框架 ActiveMQ kafka 全文搜索引擎 ElasticSearch DSL语法 Kibana 微服务架构 Spring Boot Spring Cloud 开发...
标签: computer 杂谈 声明:本页所发布的技术文章及其附件,供自由技术传播,拒绝商业使用。本页文章及其附件的所有权归属本文作者,任何使用文档中所介绍技术者对其后果自行负责,本文作者不对其承担任何责任。...
第6节杂谈 [免费观看] 00:12:37分钟 | 第7节Java的发展历史00:27:24分钟 | 第8节Java的发展历史续00:02:27分钟 | 第9节Java技术体系00:08:46分钟 | 第10节jdk8的新特性00:07:31分钟 | 第11节lanmbda表达式简介...
第6讲 杂谈 免费 00:12:37 第7讲 Java的发展历史 00:27:24 第8讲 Java的发展历史续 00:02:27 第9讲 Java技术体系 00:08:46 第10讲 jdk8的新特性 00:07:31 第11讲 lanmbda表达式简介 00:07:02 ...
技术基础 New Folder 多样式星期名字转换 [Design, C#] .NET关于string转换的一个小Bug Regular Expressions 完整的在.net后台执行javascript脚本集合 ASP.NET 中的正则表达式 常用的匹配正则表达式和实例 ...