我有一个应用程序,其中大多数操作需要一些时间,我希望始终保持GUI响应。用户触发的任何操作的基本模式如下:
我尝试了几件事来实现这一目标,但从长远来看,所有这些都会导致问题(在某些情况下看似随机访问违规)。
Synchronize
在主线程中调用OnFinish
事件。PostMessage
通知GUI线程结果已准备好。Application.ProcessMessages
)直到后台线程完成,然后继续显示结果。 我无法提出另一种选择,但这对我来说都不是很完美。这样做的首选方法是什么?
答案 0 :(得分:5)
您可以使用OTL作者here
所展示的OTL来实施质疑模式答案 1 :(得分:5)
1)采用'Orignal Delphi'方式,强制后台线程等待同步方法执行,并使系统暴露于比我满意的更多死锁潜力。 TThread.Synchronize至少重写了两次。我曾经在D3上使用过一次,并且遇到了问题。我看看它是如何工作的。我再也没用过它。
2)我经常使用的设计。我使用app-lifetime线程(或线程池),创建线程间通信对象,并使用基于TObjectQueue后代的生产者 - 消费者队列将它们排队到后台线程。后台线程对对象的数据/方法进行操作,将结果存储在对象中,并在完成时将PostMessage()对象(强制转换为lParam)返回到主线程,以便在消息中显示结果的GUI-处理程序,(再次抛出lParam)。然后,主GUI线程中的后台线程永远不必对同一个对象进行操作,也不必直接访问彼此的任何字段。
我使用GUI线程的隐藏窗口(使用RegisterWindowClass和CreateWindow创建),用于PostMessage的后台线程,LParam中的comms对象和'target'TwinControl(通常是TForm类),作为WParam。隐藏窗口的普通wndproc只使用TwinControl.Perform()将LParam传递给表单的消息处理程序。这比将对象直接PostMessaging到TForm.handle更安全 - 遗憾的是,如果重新创建窗口,句柄会发生变化。隐藏窗口从不调用RecreateWindow(),因此它的句柄永远不会改变。
生产者 - 消费者队列'从GUI',线程间通信类/对象和PostMessage()'到GUI'将运行良好 - 我已经这样做了几十年。
重新使用comms对象也相当容易 - 只需在启动时在循环中创建一个加载(最好是在初始化部分,以便使所有表单的comms对象都活得更长),并将它们推送到PC队列 - 这就是你的池。如果comms类具有池实例的私有字段,则更容易 - 'releaseBackToPool'方法则不需要参数,如果有多个池,则确保始终将对象释放回自己的池。
3)无法真正改善David Hefferman的评论。只是不要这样做。
答案 2 :(得分:1)
您可以将线程之间的数据作为消息进行通信。
线程1:
线程2:
如果您希望GUI不仅可以使用,而且还可以响应某些输入,而您正在处理需要很长时间处理的输入,则最终可能会有超过1个非GUI线程。