8

我有一个应用程序,其中大多数操作都需要一些时间,我希望始终保持 GUI 响应。用户触发的任何动作的基本模式如下:

  1. 准备动作(在主线程中)
  2. 执行动作(在后台线程中,同时保持 gui 响应)
  3. 显示结果(在主线程中)

我尝试了几件事来实现这一点,但从长远来看,所有这些都会导致问题(在某些情况下似乎是随机访问违规)。

  1. 准备动作,然后调用后台线程,在后台线程结束时,Synchronize用于调用OnFinish主线程中的事件。
  2. 准备动作,然后调用后台线程,并在后台线程结束时PostMessage通知 GUI 线程结果已准备好。
  3. 准备动作,然后调用后台线程,然后忙等待(调用时Application.ProcessMessages)直到后台线程完成,然后继续显示结果。

我想不出另一种选择,而且这些都不适合我。这样做的首选方法是什么?

4

3 回答 3

5

您可以使用 OTL 作者在此处演示的 OTL 来实现所质疑的模式

于 2012-09-14T13:32:13.063 回答
5

1) 是“原始 Delphi”方式,强制后台线程等待同步方法执行完毕,并使系统面临比我满意的更多死锁可能性。TThread.Synchronize 至少被重写了两次。我在 D3 上使用过一次,但遇到了问题。我看了看它是如何工作的。我再也没有用过它。

2)我最常使用的设计。我使用应用程序生命周期线程(或线程池),创建线程间通信对象,并使用基于 TObjectQueue 后代的生产者-消费者队列将它们排队到后台线程。后台线程/s 对对象的数据/方法进行操作,将结果存储在对象中,完成后,PostMessage() 对象,(转换为 lParam)返回到主线程,以便在消息中以 GUI 显示结果-处理程序,(再次将 lParam 投射回去)。主 GUI 线程中的后台线程就不必对同一个对象进行操作,也不必直接访问彼此的任何字段。

我使用 GUI 线程的隐藏窗口(使用 RegisterWindowClass 和 CreateWindow 创建)作为 PostMessage 的后台线程,LParam 中的 comms 对象和“目标”TwinControl(通常是 TForm 类)作为 WParam。隐藏窗口的简单 wndproc 只使用 TwinControl.Perform() 将 LParam 传递给表单的消息处理程序。这比将对象直接发送到 TForm.handle 更安全 - 不幸的是,如果重新创建窗口,句柄可能会发生变化。隐藏窗口从不调用 RecreateWindow(),因此它的句柄永远不会改变。

生产者 - 消费者队列“从 GUI 出来”、线程间通信类/对象和 PostMessage()“进入 GUI”将运行良好 - 我已经这样做了几十年。

重用通讯对象也相当容易——只需在启动时在循环中创建一个负载(最好在初始化部分中,以便通讯对象比所有形式都更有效),然后将它们推送到 PC 队列中——这就是你的池。如果 comms 类有一个用于池实例的私有字段,那就更容易了——“releaseBackToPool”方法不需要参数,如果有多个池,则确保对象总是被释放回它们自己的池。

3)不能真正改善大卫赫弗曼的评论。只是不要这样做。

于 2012-09-14T13:48:05.553 回答
1

您可以在线程之间将数据作为消息进行通信。

线程1:

  1. 为数据结构分配内存
  2. 填入
  3. 使用指向此结构的指针向 Thread2 发送消息(您可以使用 Windows 消息或实现队列,确保其入队和出队方法没有竞争条件)
  4. 可能会收到来自 Thread2 的响应消息...

线程2:

  1. 从 Thread1 接收带有指向数据结构的指针的消息
  2. 消费数据
  3. 释放数据结构的内存
  4. 可能以类似的方式将消息发送回 Thread1(可能重用数据结构,但您不会释放它)

如果您希望您的 GUI 不仅是实时的,而且还响应一些输入,那么您最终可能会得到超过 1 个非 GUI 线程,而处理需要很长时间才能处理的输入。

于 2012-09-14T13:10:01.880 回答