TExternalThread是什么?在终止基于线程的定时器时出现“无法终止外部创建的线程”错误。

13

在我设计的应用程序中,我在表单上放置了一个TLMDHiTimer控件并将其Enabled属性设置为true。但当我关闭应用程序时,有一半的时间会发生下面这种情况。在我的OnFormClose事件中,我调用MyLMDHiTimer.Enabled := false。当调用此代码时,有时会(大约一半的时间)出现异常。

我进行了调试并跟踪进入调用,发现该错误是在LMDTimer.pas文件的第246行引起的。

FThread.Terminate;
我正在使用最新版本的LMDTools。在周末之前,我完全重新安装了LMD工具,并且已经正确地将组件从表单中删除并重新添加。

据我所知,这与TExternalThread有关,但是Embarcadero没有任何文档,而且在LMDTools源代码中也没有找到任何引用它的内容。

完全更新的RAD Studio 2010、Delphi 2010。

真正让我不爽的是没有文档。谷歌只给出了一个结果,其中有人说错误是由尝试终止TExternalThread而引起的。 但是,在查看LMDHiTimer的源代码时,它只创建常规的TThread,根本没有努力终止TExternalThread。 我找到的那个谷歌结果Thread: Cannot terminate an externally created thread? 在Embarcadero上提到使用GetCurrentThread()和GetCurrentThreadId()获取必要的数据以连接到现有线程,但TLMDHiTimer并没有这样做。它只创建自己的TThread派生类,具有自己的Create()构造函数(当然重写了,并在构造函数开始时调用inherited)

那么...TExternalThread到底是什么?其他人遇到过这种异常吗?也许还发现了解决方案或解决方法? 我几乎向LMDTools的支持部门提出了完全相同的问题,但是询问多个地方也不会有坏处。

非常感谢您提供的任何帮助。

2个回答

12

TExternalThread封装了Delphi运行时库没有创建的线程。它可能代表属于操作系统线程池的线程,或者是由程序中的另一个DLL创建的线程。由于线程正在执行不属于相关TExternalThread类的代码,因此Terminate方法无法通知线程停止。

Delphi TThread对象会将其Terminated属性设置为True,并且被覆盖的Execute方法将被期望定期检查该属性,但由于此线程是非Delphi代码,因此不存在Execute方法,任何Terminated属性也只是在线程代码已经在其他地方编写之后才存在。

新闻组线程建议您的情况可能发生了什么:

...你的内存已经损坏,导致TThread.FExternalThread成员变为非零值。

这可能是组件库中的错误,也可能是您自己代码的错误。您可以使用调试器的数据断点来尝试查找。在计时器线程的构造函数中设置断点。当程序在那里暂停时,请使用“运行”菜单上的“添加断点”命令,使用新对象的FExternalThread字段的地址添加数据断点。此后,如果该字段的值发生更改,调试器将暂停并显示更改的内容。(每次运行程序时,数据断点都会被重置,因为IDE假定对象不会在相同的地址上分配。)


我按照你说的做了,但是唯一触发这个断点的时间是在应用程序关闭时组件被销毁。我按下Shift+F7跟踪源代码中的下一步(使用调试dcus,所以我可以看到所有底层的delphi类),调用堆栈如下:http://pastebin.org/88904在我看来,这似乎只是对表单组件进行常规销毁。 - Michael Stahre
你展示的调用堆栈并不是计时器组件的销毁。看起来像是某个表单的销毁。FExternalThread字段仍然为False吗?当数据断点触发时,那块内存包含了什么新值? - Rob Kennedy
是的,看起来我的应用程序主窗体被破坏了。计时器组件被放置在上面,这让我解释调用堆栈时组件在窗体销毁期间被销毁(窗体应该在销毁期间销毁其上的所有组件,对吧?)当我评估FExternalThread地址的值时,它总是为true,即使Delphi调试器本身说它为false。 我写下@FExternalThread并进行Boolean($Addess)评估,在创建时甚至只有FExternalThread返回'false',但结果却是'true'。 - Michael Stahre
实际上,不用了,我成功地使用PBoolean($address)^正确检索到了数据断点触发时的值,并且它确实被设置为true。编辑:不,其他电脑也会出现这种情况,所以我猜应该是这个应用程序的问题?尽管如此,数据断点只在销毁期间触发,而数据在那时已经被修改,这还是很奇怪的。所以我的值已经被更改了,而我的应用程序没有更改它吗?我要尝试在另一台计算机上运行此应用程序,看看是否也会出现相同的错误。 - Michael Stahre
我在另一台电脑上运行了它,没有任何区别。 我刚刚逐步执行了我的代码,一直到应用程序的.dproj的最后一行,并发现在线程创建期间FExternalThread地址处的值始终为false :/ - Michael Stahre
在释放资源时出现了一个错误,需要加1。当我关闭句柄代码不正确时,我遇到了这个问题。 - Fr0sT

5
有没有可能代码正在尝试终止已经销毁的TThread?如果设置了FreeOnTerminate,这很容易发生。
我在诊断一个类似(相反?)的错误时注意到了您的帖子,“无法在运行或暂停的线程上调用Start”在组件的构造函数中放置在主表单上。当我删除了Start()之后,该错误被更具说明性的错误所取代,例如“无效的指针操作”和“访问冲突”,在相应的析构函数中。组件在TThread被释放后尝试操作其TThread对象,因此留下了事情交给了墨菲定律。当我解决了这个问题之后,我能够替换Start()调用而不会出现“无法调用Start”的错误返回。
类比一下,你的问题是否是FExternalThread的地址在析构/终止调用之前被回收和破坏了?在我们的情况下,我们有一个有缺陷的Singleton Instance Pattern实现;但同样,FreeOnTerminate也似乎是一个可能的嫌疑犯。
[FYI:我在RAD Studio XE下使用C++]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接