Delphi XE中是否可以在次要线程中接收事件?

5
我希望在一个示例应用程序中有三个线程。
线程#1(主线程)- 用户界面/GUI
线程#2 - 与串行端口设备绑定,通过事件接收数据并传递到数据队列。
线程#3 - 当队列条目被创建时激活,处理数据节点,释放数据对象。
目标是:
a) 防止当鼠标按住主窗体上的按钮或表单时丢失数据。
b) 快速获取事件中的数据,将其存入队列,然后返回睡眠状态。
c) 在有数据时处理数据,否则睡眠。
像AsyncoPro这样的软件包能够将事件处理与非主线程绑定吗?
我从未使用过串口事件驱动的应用程序,大部分都是轮询的,我想做一些测试。

AsyncPro 是用于串口通信的,而不是事件处理。我对你提到 AsyncProFeeling confused. - Warren P
@Warren:我个人对这个库不太熟悉,但是如果我要猜的话,我会说它可能在接口中使用了事件处理程序(回调函数)。 - Mason Wheeler
我假设关键点是事件将会留在组件创建时所在的线程上?我们假设这是一个编写正确的非可视化组件。 - Rich Shealer
所以您想要从 AsyncPro 的主线程“传输”事件到后台线程?在后台线程中实例化像 AsyncPro 这样的非可视控件并在该后台线程中运行所有调用是安全的。但是,我认为在 AsyncPro 的情况下,它将始终在主线程中触发其事件处理程序,因为它已经明确设计为这样做。您应该使用一个小测试应用程序来验证这个假设。 - Warren P
@Warren:是的,那就是要点。我们使用的自制库在Unicode成为标准字符串之前做得很好。转移到XE是一个目标,但需要进行很多更改,我正在考虑重新开始,并从轮询接口转移到事件驱动接口,这是非常重要的。 - Rich Shealer
你能否考虑放弃使用AsyncPro,采用TComPort代替呢?TComPort非常简单又轻巧,可以很容易地处理串口通信,最好的方式是通过一个简单的长期运行的工作线程在第二线程上下文中处理所有串口通信,然后当有数据可用时通知UI。如果你有关于串口工作线程及其如何通知主线程的问题,我一定可以帮忙。【你是否正在发送Unicode数据到com端口?】 - Warren P
3个回答

6
你绝对可以将事件处理程序绑定到非主线程。你不能将屏幕更新与非主线程绑定。Windows API不是线程安全的,因此建立在Windows API之上的Delphi VCL也不是线程安全的。但是,你的设计基本上是一个好的、可行的想法;只需要记住使用TThread的Synchronize或Queue方法将任何UI更新发送回执行在主线程上。

4
许多Windows API中的部分以一种或另一种形式是线程安全的。线程安全并没有单一的意思。若未明确表明该术语的含义,则说某物是否线程安全是不精确的。对于Windows GUI代码而言,窗口句柄具有线程亲和性。因此,所有接收窗口句柄的API调用必须从创建窗口的线程中进行。从这个规则可以得出一个结论:VCL组件的操作应该从主应用程序线程中执行。 - David Heffernan
3
据我所知,Windows API 线程安全的,除非在MSDN中有相反的说明。特别是自 Windows 的早期版本以来,所有 GDI、文件、内存和转换过程都是线程安全的。它甚至是进程安全的:你可以从一个进程向另一个进程发送GDI消息,没有问题。VCL的UI部分 不是 线程安全的,而大多数与非UI相关的部分 线程安全的。 - Arnaud Bouchez
@Mason - 很好的观点。在我早期编写线程代码的时候,我遇到了从错误线程修改VCL组件的问题。 - Rich Shealer

5
最简单的方法应该是定义一些用户消息,然后从子线程发送到主线程。
它完全是线程安全的,甚至是进程安全的。
使用PostMessage()和主表单的句柄。但不要将此WM_USER+n消息广播到整个UI,因为您可能会混淆一些定义自己的自定义消息的VCL的部分。
如果您想在线程或进程之间复制一些文本数据,则可以查看WM_COPY_DATA。实际上,这非常快,对于小消息来说比命名管道更快。
对于用户界面,我发现无状态实现有时是一个好主意。也就是说,您不通过Synchronize()调用或GDI消息回调主线程,而是您的主GUI线程具有一个定时器,该定时器检查共享内存缓冲区以获取待处理更新。这就是Web的工作方式,在实践中,它非常容易使用:您不必编写任何回调,每个线程都是独立的,完成自己的工作,并在必要时刷新。
但是,解决方案当然取决于您的确切项目架构。

如果需要一个简单却经过验证的库,请查看AsyncCalls,它适用于从Delphi 5到XE的版本。对于最新版本的IDE(Delphi 2007及更高版本),可以看看OmniThreadLibrary。使用这些库,您将确保您的软件实现不会在任何地方出现问题:多线程应用程序在大多数情况下都能正常工作,但由于未知原因,有时会陷入无限循环。当然,这种情况只会发生在客户端,而不是在你的电脑上... 如果你不想花费数小时来调试程序,只需信任那些经过验证的库,这些库已被证明设计得非常好且经过了调试。


@Bouchez - 我会研究AsyncCalls。AsyncPro在这里已经工作了约10年,但是由于分裂和缺乏真正的赞助商,使用Unicode字符串似乎很困难。这并不是对那些试图维护它的人的攻击。我使用了Martin Harvey的线程库ThreadNotify.pas,使得在线程上进行VCL更新更加容易,但我看到了很多关于OTL的赞誉。 - Rich Shealer

1

当然,你可以以一种或另一种方式做到这一点。 我自从D5以来就没有使用Apro,我有的Apro在我的D2009上无法工作(unicode / string / ANSIstring问题),而且我有自己的串行类。 大多数可用的串行组件都可以选择在rx线程或主GUI线程上触发dataRx事件-显然在你的情况下,应选择rx线程(线程#2)。 将rx数据推送到某个缓冲区类中,并将其推送到生产者消费者线程以(线程#3)进行处理。 在那里处理它。 如果您需要从那里进行GUI更新,请向GUI线程引用PostMessage并在用户定义的消息处理程序中处理它。

我已经完成了这种类型的许多工作-它会正常工作。

此致, 马丁


我猜我的知识不足,导致事件在第二个线程上触发。这可能只是简单地在第二个线程上创建组件,然后它们就会在那里触发。 - Rich Shealer
Apro和大多数其他串口组件都使用内部线程从端口读取数据 - 这个读取线程是您的线程#2。这个读取线程通常可以配置为直接触发读取事件或通过TThread.synchronize()机制触发,即通过某个布尔属性使主GUI线程安全。哪个线程创建了组件并不重要。在您的情况下,您可以在GUI线程中创建组件(即将其放在表单上),但只有在创建线程#3后才能激活它。 - Martin James
如果我没记错的话:Apro包含明确的逻辑来确保事件在主线程上触发!所以Rich,AsyncPro设计会与你作斗争。正是出于这样的原因,多年前我放弃了AsyncPro并使用TComPort {由Dejan Crnila开发,后来由我继续}或Pascal中的普通Win32串行库。 - Warren P
@Warren P:看起来你是对的 - 我不得不回到 D5 去检查它,但是 Apro 似乎没有任何选项可以直接从内部 rx 线程触发事件 :( 我也使用过 TComPort,但我拥有的那个似乎在 D2009 中无法工作,所以像你一样,我使用了自己的库。 - Martin James
AsyncPro 中甚至存在更糟糕的问题,最终导致我放弃使用它。 - Warren P
显示剩余2条评论

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