将函数移动到没有异步事件的单独工作线程。
主线程中的异步事件应该将一些消息发布到某个队列中,因此当工作线程完成执行时,主线程将使用新参数再次启动它。
有更多信息的更新。假设(猜测)这些函数可以通过 RS232 接收到的数据或什么都没有,并且正在执行类似于 GUI 更新的操作,我将概述以下方法。
- 使用窗体的 Windows 消息队列进行警报。
- 使用一些互锁的队列对象进行数据传递。(是的,我知道我可以将指针放入 Windows 消息中,但我想要更多的类型安全性)
- 使用外工作螺纹进行长加工。
更多细节:
- grep WM_USER 的 VCL 源代码并查看模式。您声明了 3 个消息 ID 常量:Work_Complete、Work_Request、Work_EmptyQueue。以及相应的消息方法形式。
- 我会使用来自 OmniThreadLibrary 的就绪线程安全队列。但是您可以继承 System.Contnrs.TQueue 或 System.Generics.Collections.TQueue,将它们所有的数据传递方法封装到临界区。总的来说,我建议你看看 OmniThreadLibrary 管道,如果它们适合你的话
- 这是硬件数据工作应用程序的标准方式。无论是 MS-DOS 设备驱动程序。或者一些嵌入式设备。您应该将快速和精益的数据保存与长期工作区分开来。
因此,RS232 事件处理程序将从 COM 读取数据,将其放入 TBytes,然后将该数据包添加到输入 TQueue。更复杂一点的方法是查看 Queue 是否已经包含来自 COM 的数据并将新数据包与旧数据包聚合,而不是将新数据包分开。这需要更仔细的锁定,所以在这里聚合可能不值得
计时器甚至会制作空包(零长度字节数组)并将其排入队列。如果该计时器甚至还有一些数据要传递-那么它应该是变体记录或单独的输入队列或其他任何东西。但是没有信息,似乎计时器只发送任何信息,但警报本身
根据您的说法,Timer 和 RS232 事件都在各自的线程中工作。我对此表示怀疑,但我必须相信你。
在将工作负载排入队列后(例如在输入队列的通知事件中),我会执行 win32 PostMessage(MainForm.Handle,work_request)。毕竟我们希望将线程控制集中在一个地方。为了保持线程隔离,应该发布消息,没有 SendMessage 没有 TControl.Perform!
在表单的 work_request 处理程序的主线程中,我会查看输入队列是否已经不是空的。如果没有,那么我会查看工作线程状态。如果它被暂停,我会恢复它。
工作线程看起来是输入队列有东西,虽然它有,但它会:
- 从队列中提取数据包到本地变量
- 做一些可能很长的处理
- 制作输出“工作成果”包。假设是 GUI 更新,它可能只是键值对的集合,或者一些数据字段的记录,其中包含哪些数据字段已填充,哪些数据字段将被忽略。
- 将该数据包排入输出队列(排队然后将 Work_Complete msg 发布到主窗体)
- 循环到 1。
如果输入队列为空,则函数退出,线程执行 PostMessage Work_EmptyQueue 并暂停自身,直到稍后被唤醒以完成更多工作或被释放。
当 MainForm (再次在主线程中)收到 Work_Complete 时
- 将输出队列中的所有数据包提取到本地变量中
- 合并它们。(如果我有 p1=(label1='aaaa', label2='bbbb') 和 p2=(label3='cccc', label1='dddd') 那么累积的任务将被设置(label1='dddd', label2 ='bbbb', label3='cccc');
- 应用它们(实际上是更新 GUI)
如果输出队列为空,则省略第 2 步和第 3 步(如果在上一步发生合并)。但不是第四。第 1 步和第 2 步是分开的。由于队列操作是线程互锁的,因此步骤 1 的目标是尽可能快地提取数据。保证金将在当地完成。
当 MainForm 收到 Work_EmptyQueue 时,它会检查输入队列现在是否不为空,并且可能会恢复工作线程。它可以选择对状态或其他任何东西进行一些 GUI 更新。
那是一个粗略的草图。它可以更好地雕刻给你一定的引用。