【问题标题】:How to access the UI controls from another thread?如何从另一个线程访问 UI 控件?
【发布时间】:2015-04-11 07:17:21
【问题描述】:

我知道为了从另一个线程访问 UI 控件,我应该使用PostMessage()。但是,PostMessage() 是异步的,因此例如,如果我尝试更改 "EDIT" 控件的文本,我将无法在完成后删除文本缓冲区,因为我不知道窗口过程何时会完成处理消息。

所以这是我能想到的从另一个线程访问 UI 控件的两个想法:

  • 创建一条用户定义的消息,例如,我向 UI 线程发送如下内容:“将这 200 行(全部存储在一个字符串中,由 '\n' 或其他内容分隔)插入ListViewX ”,当 UI 线程收到这条消息时,它会更新ListViewX,完成后,它会删除字符串(分配在堆上)。
  • 另一种方法是将访问 UI 控件的代码放在函数内部,并将函数指针发送给 UI 线程,然后由 UI 线程调用。

其中一种方法是否比另一种具有一些优势,是否有其他方法可以做到这一点?

【问题讨论】:

  • 如果线程阻塞直到 UI 线程处理完消息是可以接受的,SendMessage 是另一种选择。
  • @Harry Johnston 我已经读到SendMessage() 可能会导致死锁(这并不总是很容易预测)。请参阅:flounder.com/workerthreads.htm,部分:工作线程和 GUI II:不要触摸 GUI
  • 我不同意那篇文章对问题的解释。发生死锁是因为 GUI 线程正在等待工作线程完成,并且以这样一种方式实现了等待,即 GUI 消息在此期间没有被处理。你不应该这样做,因为即使你没有死锁,GUI 也会被冻结,直到工作线程完成。免责声明:我不是 GUI 编程专家。
  • 是的,我认为在 UI 线程上等待是一种不好的编程习惯。因此,当我开始编程时,我在工作中遇到了一些麦芽汁经验。

标签: multithreading winapi


【解决方案1】:

我经常使用以下模式(箭头表示“使用”关系):

           +---------------+                     
           | Communication |        +-----------+
      +--->+  Data Object  +<---+---+ Thread #0 |
      |    | (thread safe) |    |   +-----------+
      |    +---------------+    +---+ Thread #1 |
      |                         |   +-----------+
      |                         |     ...        
+-----+----+                    |   +-----------+
| Main (UI)|                    +---+ Thread N  |
|  Thread  |                        +-----------+
+----------+                                     

Communication Data 对象是线程安全的,通常是ref-counted(对于非 GC 语言很重要)。该对象提供了几个典型的方法(都是可选的,取决于实际用例):

  • 工作项队列
  • 需要交换的任何其他数据的数据获取器/设置器
  • 在 UI 中可视化的进度信息
  • 中止/状态标志,结合 ...
  • 可等待事件对象发出各种信号

因为数据对象完全照顾自己,包括同步对其数据的访问,以及由于引用计数而或多或少地自动清理,所以您最终会将所有线程内容很好地封装到一个专用类中。我发现这在各种情况下都非常方便。

此外,该方法将工作线程与 UI 完全分离,因此线程甚至根本不知道是否存在 UI 以及它的外观:它是 GUI ?它是 CLI 吗?也许是网络服务?最后同样重要的是,它保持 UI 响应,因为 UI 线程(或等效线程)可以完全自行决定何时以及多久更新一次 UI。


PS:可能有官方GOF名称,不知道。

【讨论】:

  • 只是指出两种方法之间的区别:一种方法是工作线程阻塞,直到请求到达主 UI 线程并再次返回;另一种是“即发即弃”,其中通信对象将是线程安全的 FIFO 或不期望响应的 Windows 消息
  • 它本身不是 FIFO。它可以是您想要的任何东西,包括 FIFO,但不限于此。这里最重要的一点是关于解耦,特别是工作线程不了解 UI。想象一下,您想在服务器应用程序中使用那段代码,例如一个网络服务。没有HWNDSendMessage() 到。但是由于我们解耦了整个事情,您只需要编写与桌面应用程序中的 UI 线程线程相关的部分,而且在任何地方都不会涉及阻塞等待。
【解决方案2】:

我知道为了从另一个线程访问 UI 控件,我应该使用 PostMessage()。但是,PostMessage() 是异步的,因此例如,如果我尝试更改“EDIT”控件的文本,完成后我将无法删除文本缓冲区,因为我不知道窗口过程何时完成处理消息。

你错了。没有强制使用PostMessage。实际上,对于设置控件的窗口文本,您不应该使用PostMessage。由于您概述的原因,您需要同步发送WM_SETTEXT。如果你不同步发送,那么你不知道什么时候销毁文本缓冲区。

你需要做的如下:

  • 如果窗口在您的进程中,那么您应该使用SetWindowText
  • 如果窗口处于不同的进程中,则应使用SendMessageTimeout 发送WM_SETTEXT 消息。

对于不同进程中的窗口,SetWindowText 被记录为不起作用。它比Raymond explains 更复杂一些,但您仍然通常不应该在不同进程的窗口上使用它。因此,对于不同进程中的窗口,使用SendMessageTimeout 发送WM_SETTEXT。超时是为了防止你的应用程序在目标应用程序挂起时挂起。

【讨论】:

  • "如果你不同步发送,那么你不知道何时销毁文本缓冲区" 我使用PostMessage() 发送用户定义的消息而不是发送WM_SETTEXT ,在 UI 线程中我用WM_SETTEXT 调用SendMessage(),当SendMessage() 返回时,我销毁文本缓冲区。
  • "我正在使用 PostMessage() 发送用户定义的消息" - 很好,只要消息参数不涉及需要清理的指针。如果是这种情况,您又会遇到同样的问题,只是消息 ID 不同。 PostMessage() 是异步的,这引发了谁何时清理的问题。如果窗口消失了,没有人收到消息怎么办?内存泄漏?这很难管理和维护,并且容易出错。我不会那样做。
  • @JensG 我在用户定义的消息中发送一个指向缓冲区的指针,然后我从 UI 线程调用 SendMessage(),然后在 SendMessage() 返回时删除缓冲区。我不明白这怎么行不通!至于窗口在消息被处理之前被销毁,这不会发生,因为我正在将用户定义的消息发送到主窗口(所以它被销毁的唯一方法是关闭应用程序)。
  • 请注意,我没有说“它永远不会工作”。我说过“很难维护并且容易出错”,尤其是所有错误的边缘情况。最后是你的选择。
  • @JensG 问题是我不明白它是如何容易出错的。顺便说一句,我从这里得到了这个想法:flounder.com/workerthreads.htm,请参阅以下部分:工作线程和 GUI II:不要触摸 GUI
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多