可问题是,如果正在编译时修改了一个文件,会出现什么情况呢?为解决这个问题,你可以临时复制那个文件的副本,或者干脆禁止用户在编译期间修改尚未编译的文件。这说明,线程可以解决一些
问题,但同时也带来了其他问题。一个更为严重的问题是多线程依赖于时间片,在多线程中的缺陷非常难以调试。同时,编写和实现线程安全的代码也比较困难,程序员需要考虑很多的因素。
11.2 TThread 对象
D elphi把有关线程的A PI封装在TThread 这个Object Pascal的对象中。虽然T Thread已经封装了几乎所有与线程有关的A PI。但在某些情况下,尤其在处理线程同步的问题时,仍需调用一些别的A PI函数。本节将介绍TThread 用法。11.2.1 TThread基础
下面是T Thread在C lasses单元中的声明:
正如你看到的一样,T T h r e a d的派生类中唯一必须覆盖的方法是T Te stThread中进行复杂的计算,可以如下定义E xecute():
E x e c u t e ( )。假设你要在
当然,本段代码只是为了演示一个长循环。本线程的唯一目的是解决长时间的计算工作。
现在,可以通过调用线程对象的C reate()使上述代码执行。在下例中,看到在主窗体上有一个按钮,
单击此按钮就会调用C reate()(请注意,不要忘记在主窗体单元的u ses子句中包含T Te stThread单元,否
则会导致编译错误)
。
如果运行此程序并单击按钮,上述的那个长循环就会执行,但同时,你会注意到仍然可以操纵窗口,做各种其他操作。
注意当T Thread的C reate()被调用时,需要传递一个布尔型的参数C reateSuspended。如果把这
个参数设成F alse,那么当调用C reate()后,E xcute()会被自动地调用,也就是自动地执行线程代
码。如果该参数设为Tr ue,则需要运行T Thread的R esume()来唤醒线程。一般情况下,当你调用C reate()后,还会有一些其他的属性要求设置。所以,应当把C reateSuspended参数设为Tr ue,因为在TThread已执行的情况下设置TThread的属性可能会引起麻烦。
再深入一点讲,在构造函数C reate()中隐含调用了一个RT L例程B eginThread(),而它又调用了一个A PI函数C reateThread()来创建一个线程对象的实例。C reateSuspended参数表明是否传递CREATE_ SUSPEDED标志给CreateThread()。
11.2.2 TThread实例
回到T Te stThread对象的E xcute(),我们注意到它声明了一个局部变量i ,试想一下,当存在两个TTestThread对象的实例时,i的值会怎么样呢?它会覆盖掉另一个实例的值吗?或者第一个实例具有优先级?回答是否定的。因为Wi n32会为每个线程都分配一个单独的栈。这意味着,当你创建了TTestThread对象的多个实例后,在每个线程实例所在的栈里都有一份i 的副本。因此,所有的线程都是独立地运行的。
然而,上述内容并不适合于线程中的全局变量。在本章的11 .3节中我们将讨论此问题。11.2.3 线程的终止
当线程对象的E xcute()执行完毕,我们就认为此线程终止了。这时,它会调用D elphi的一个标准例程E ndThread(),这个例程再调用A PI函数E xitThread()。由ExitThread() 来清除线程所占用的栈。
当结束使用T Thread对象时,应该确保已经把这个Object Pascal对象从内存中清除了。这才能确保所有内存占有都释放掉。尽管在进程终止时会自动清除所有的线程对象,但及时清除已不再用的对象,可以使内存的使用效率提高。利用将F reeOnTe rminate的属性设为Tr ue的方法来及时清除线程对象是最方便的办法,这只需要在E xcute()退出前设置就行了。设置方法如下:
这样,当一个线程终止时,就会触发O nTe rminate事件,就有机会在事件处理过程内清除线程对
象了。
提示OnTerminate事件是在主线程的环境中发生的。这意味着,在处理这个事件的处理过程中,
你可以不需要借助于Synchronize()而自由地访问