【问题标题】:Change GUI in thread在线程中更改 GUI
【发布时间】:2014-02-21 07:35:03
【问题描述】:

我有一个大约 20 秒后结束的手术。为了避免冻结,我想创建一个线程并每秒更新一个标签文本。查了很多,因为每个人意见不一,所以没办法决定用什么方法。

我尝试了 SendMessage 并且它有效,但有些人认为使用 SendMessage 不安全,我应该改用 PostMessage。但是 PostMessage 失败并显示 ERROR_MESSAGE_SYNC_ONLY (1159)。

char text[20] = "test text";
SendMessage(label_hwnd, WM_SETTEXT, NULL, text);

我对此进行了搜索,我认为这是因为在 PostMessage 中使用了不允许的指针。这就是它失败的原因。

那么,我该怎么办?我糊涂了。你有什么建议?这种方法对改变其他线程中的 UI 元素有好处吗?

谢谢

【问题讨论】:

  • “有些人”有没有提到为什么它不安全?

标签: c++ multithreading mfc thread-safety sendmessage


【解决方案1】:

ERROR_MESSAGE_SYNC_ONLY 的文档说:

消息只能用于同步操作。

这意味着你可以使用同步消息传递,即SendMessage和类似的,但你不能使用异步消息传递,即PostMessage

原因是WM_SETTEXT 是一条消息,其参数包含引用。参数不能按值复制。如果可以异步传递WM_SETTEXT,那么系统如何保证接收窗口收到的指针仍然有效?

因此,系统会简单地拒绝您发送此消息的尝试,并且实际上拒绝任何其他具有引用参数的消息。

您在这里使用SendMessage 是合理的。那肯定会奏效。

但是,您正在强制您的工作线程在 UI 上阻塞。 UI 可能需要一些时间来更新标题的文本。另一种方法是向 UI 线程发布一条自定义消息,指示 UI 线程更新 UI。然后您的工作线程线程可以继续其任务并让 UI 线程并行更新,而不会阻塞工作线程。

为了让它工作,你需要一种方法让 UI 线程从工作线程获取进度信息。如果进度与百分比一样简单,那么您需要做的就是让工作线程写入共享变量,并让 UI 线程读取共享变量。

【讨论】:

  • 因此,SendMessage 可以用于更改 UI 元素。您说我强制 UI 线程处理导致阻塞的消息,但我认为这没什么大不了的。正确的?顺便说一句,如何使用 PostMessage 传递文本字符串(因为您建议使用自定义消息)?
  • 是的,SendMessage 在您的问题中写的将起作用。尽管您应该将字符串声明为 const char *text = "test text"char text[] = "test text" 而不是问题中的代码。这冒着不被空终止的风险。
  • 我不明白您的推理,因为SendMessageCallback 返回相同的错误代码,并且显然将用户缓冲区传递给该函数没有问题,因为您可以在返回时正确释放它。那么是什么给了?
【解决方案2】:

嗯,错误说明了一切。消息不能异步发送。 PostMessage 的事情是,它将消息发布到侦听线程的队列并立即返回,而无需等待消息处理的结果。另一方面,SendMessage 等待窗口过程完成对消息的处理,然后才返回。

在您的情况下使用PostMessage 的风险在于,在窗口过程处理消息之前,您可能已经释放了字符串缓冲区。因此,在这种情况下使用SendMessage 会更安全,这也是 MS 开发人员在决定不允许异步发布此特定消息时可能想到的。

编辑:为了清楚起见,当然这并不能完全消除传递裸指针的风险。

【讨论】:

  • 那么,您是否建议使用 SendMessage 来更新 UI 元素?它可以更新文本框文本或更改进度条值。
【解决方案3】:

来自MSDN

如果您向异步消息函数(PostMessage、SendNotifyMessage 和 SendMessageCallback)发送 WM_USER 以下范围内的消息,则其消息参数不能包含指针。否则,操作将失败。

【讨论】:

    【解决方案4】:

    异步 ​​PostMessage() 替代方法要求将参数中传递的数据的生命周期延长到消息发起者函数之外。这样做的“经典”方法是堆分配数据, PostMessage 指向它的指针,以通常的方式处理消息处理程序中的数据,然后删除它,(或以其他方式处理它,以便它不泄漏)。换句话说,“一劳永逸” - 在 PostMessage 发出后,您不得触摸原始线程中的数据。

    好处是 PostMessage() 允许原始线程“立即”运行,因此可以进行进一步的工作(可能发布更多消息)。如果 GUI 很忙,SendMessage() 和此类同步通信可能会被阻止,从而影响整体吞吐量。

    缺点是线程生成消息的速度可能比 GUI 处理它们的速度快。这通常表现为延迟的 GUI 响应,尤其是在执行 GUI 密集型工作(如移动/调整窗口大小和更新 TreeViews)时。最终,当超过 10,000 条消息排队时, PostMessage 调用将失败。如果发现这是一个问题,则可能必须添加额外的流控制,从而使通信更加复杂,(如果所有可用对象都存在,我通常通过使用固定大小的对象池来阻塞/限制原始线程来做到这一点卡在已发布但未处理的消息中。

    【讨论】:

    • 感谢您的评论先生。这很有帮助。
    【解决方案5】:

    我认为您可以在这里安全地使用 SendMessage。那么你就不需要担心你的字符串的内存持久性和其他问题了。 当您从另一个消息处理程序发送消息或将消息发送到阻塞的 GUI 线程时,SendMessage 是不安全的,但如果您知道它是安全的 - 只需使用它

    【讨论】:

      【解决方案6】:

      这不是PostMessage 的问题,而是您发送的消息-WM_SETTEXT 的问题。首先一个常见的误解是,如果你从线程中向控件发送消息(),它与调用 GUI API 不同,它实际上是 NOT。例如,当您(从任何地方)调用 GUI API 来设置文本时,Windows 会以 SendMessage() 调用的形式实现这一点。因此,当您发送相同的消息时,它本质上与调用 API 相同。尽管像这样的直接 GUI 访问在许多方面都有效,但不建议这样做。出于这个原因,我不同意@David 接受的答案。

      正确的方法是(动态代码)

      char* text = new char[20] 
      strcpy_s(text, "test text");
      PostMessage(label_hwnd, IDM_MY_MSG_UPDATE_TEXT, NULL, text); 
      

      您将在自己的消息 IDM_MY_MSG_UPDATE_TEXT 处理函数中更新文本并删除内存。

      【讨论】:

      • 感谢您的评论。您说过“尽管像这样的直接 GUI 访问在很多方面都有效,但不推荐”。你能告诉我为什么不推荐它吗?谢谢
      • @MalikÇelik 这是一个很大的问题,但通常这取决于你在做什么..如果只有一个线程正在访问 GUI,并且它与 GUI 或其他线程没有冲突,就可以了。但是,如果您不使用线程同步,则存在忽略某些内容的风险。
      • 啊哈,所以你说的是一般线程问题。即使是全局整数,此问题也有效。线程应谨慎访问它。谢谢你扎达内
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-27
      • 1970-01-01
      • 1970-01-01
      • 2013-07-12
      相关资源
      最近更新 更多