【问题标题】:How to keep a thread's message pump reactive如何保持线程的消息泵反应
【发布时间】:2019-11-27 18:07:40
【问题描述】:

我正在实现一个需要以下功能的线程:

  1. 及时响应终止请求
  2. 发送消息
  3. 在等待消息时保持对 SendMessage 请求的响应

我最初的消息泵实现使用了GetMessage,比如:

while not Terminated and GetMessage(Msg, 0, 0, 0) do
begin
  TranslateMessage(Msg);
  DispatchMessage(Msg);
end;

我发现的问题是,除非有消息,否则 GetMessage 永远不会返回。 这意味着,如果消息活动较少,它可能需要很长时间才能再次检查Terminated

我的第二个实现(受this answer 启发)使用MsgWaitForMultipleObjects 等到消息存在后再检查(因为它有超时)

while not Terminated do
begin
  if MsgWaitForMultipleObjects(0, nil^, False, 1000, QS_ALLEVENTS) = WAIT_OBJECT_0 then
  begin
    while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do
    begin
      TranslateMessage(Msg);
      DispatchMessage(Msg);
    end;
  end;
end;

我发现的问题是MsgWaitForMultipleObjects 在等待时阻塞了线程。因此,当通过SendMessageTimeout 向线程发送消息时,它会超时,而使用GetMessage 时则不会。

想到的解决方案是回到GetMessage 实现,但添加一个计时器以确保WM_TIMER 消息每秒重置循环。

这真的是唯一的方法吗? 似乎应该有一些更好的方法来让线程在等待消息时保持响应。

【问题讨论】:

  • 使用第一个消息循环并在终止线程时发布 WM_NULL。如果 GetMessage 被阻塞,它将立即返回。应删除您的要求列表中的第 3 项。当无事可做时,保持清醒是没有意义的。
  • 第 3 项是指在使用 MsgWaitForMultipleObjects 时如何丢弃 SendMessageTimout 请求。它需要能够响应这些消息。
  • 我的评论中勾勒的解决方案没有问题
  • 我同意。我猜你是想说它措辞不好。我已经重新措辞了。

标签: multithreading delphi winapi


【解决方案1】:

我最初使用 GetMessage 实现的消息泵:

while not Terminated and GetMessage(Msg, 0, 0, 0) do
begin
  TranslateMessage(Msg);
  DispatchMessage(Msg);
end;

我发现的问题是,除非有消息,否则 GetMessage 永远不会返回。这意味着,如果消息活动较少,它可能需要很长时间才能再次检查Terminated

您可以覆盖线程的虚拟TerminatedSet() 方法,通过PostMessage()PostThreadMessage() 将消息发布到队列,以“唤醒”GetMessage()(如果它被阻止)。 p>

或者,让您的线程构造函数创建一个TEvent 对象,并在线程的析构函数中释放它。然后让TerminatedSet() 发出该事件的信号。然后,您的循环可以使用MsgWaitForMultipleObjects() 同时等待消息队列和事件。返回值将告诉您等待是由消息还是事件满足。

我的第二个实现(受this answer 启发)使用MsgWaitForMultipleObjects 等到消息存在后再检查(因为它有超时)

while not Terminated do
begin
  if MsgWaitForMultipleObjects(0, nil^, False, 1000, QS_ALLEVENTS) = WAIT_OBJECT_0 then
  begin
    while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do
    begin
      TranslateMessage(Msg);
      DispatchMessage(Msg);
    end;
  end;
end;

我发现的问题是MsgWaitForMultipleObjects 在等待时阻塞了线程。因此,当通过SendMessageTimeout 向线程发送消息时,它会超时,而使用GetMessage 时则不会。

SendMessage...() 系列函数将直接传递消息到目标窗口的消息过程,完全绕过消息队列。所以MsgWaitForMultipleObjects()(Get|Peek)Message() 永远不会报告来自SendMessage...()已发送 消息,只会报告来自PostMessage()PostThreadMessage()已发布 消息(或合成消息,如WM_TIMERWM_PAINT 等)。然而,当发送消息跨越线程边界时,接收线程仍然需要执行消息检索调用(即(Get|Peek)Message())才能真正发送发送消息传递给窗口过程。

想到的解决方案是回到GetMessage 实现,但添加一个计时器以确保WM_TIMER 消息每秒重置循环。

在线程内部,最好使用waitable timer而不是WM_TIMER,然后可以使用带有MsgWaitForMultipleObjects()的计时器。但实际上,GetMessage()WM_TIMERMsgWaitForMultipleObjects() 与超时之间几乎没有区别,因此无需浪费系统资源创建计时器。

【讨论】:

  • 谢谢!我喜欢最好从TerminatedSet 发消息的想法。
猜你喜欢
  • 1970-01-01
  • 2014-03-07
  • 2014-02-08
  • 1970-01-01
  • 1970-01-01
  • 2016-03-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多