【问题标题】:How to do asynchronuous programming in Delphi?如何在 Delphi 中进行异步编程?
【发布时间】:2012-09-14 12:50:47
【问题描述】:

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

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

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

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

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

【问题讨论】:

  • 我已经多次使用选项 2 没有任何问题。您是否尝试过使用 MadExcept 之类的工具来找出您的 accvios 发生在哪里?
  • 选项 1 和 2 基本上是一样的,而且效果很好。选项 3 是邪恶的。不要这样做。
  • PostMessage 来自后台线程是通知 GUI 有关结果的正确方法。你没有说这种方法有什么问题。有一些框架可以让这更容易,比如OTLAsyncCalls
  • 在 XE2+ 中支持跨平台,还有相同的选项吗?
  • 否 - API 不同 :( 那个 siad,并不意味着不能使用类似的整体设计方法。后来的 Delphi 版本有更好的内置线程和线程间通信,支持,也许到了可以使用的程度:)

标签: multithreading delphi asynchronous delphi-xe


【解决方案1】:

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”都可以正常工作 - 我已经这样做了几十年了。

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

3) 无法真正改善 David Hefferman 的评论。只是不要这样做。

【讨论】:

  • "这比将对象直接发送到 TForm.handle 更安全 - 不幸的是,如果重新创建窗口,句柄可能会发生变化。"什么可能触发重新创建窗口?只有当程序员明确地销毁并重新创建一个窗口时,还是它也可以以其他方式发生?如果它可以以其他方式发生,它是否会发生在应用程序的主窗体上?
  • 感谢您的回答!您能否详细说明异常处理如何适合您的方法?
  • @RobertFrank,当边框样式、边框图标等发生更改时,表单会重新创建句柄,另请参阅PostMessage returns “invalid window handle” in thread。 Martin James 已经发布了一个如何做隐藏窗口的示例,我会查找它。用于许多应用程序。
  • 找到了,链接在这里:PostToMainThread.
  • @LURD 感谢您花时间回答和挖掘链接。
【解决方案2】:

您可以使用OTL作者here演示的OTL实现所质疑的模式

【讨论】:

    【解决方案3】:

    您可以在线程之间以消息的形式传递数据。

    线程1:

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

    线程2:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-09-07
      • 1970-01-01
      • 2011-01-19
      • 1970-01-01
      • 1970-01-01
      • 2016-12-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多