如何保持线程的消息泵响应

iCo*_*ime 3 delphi winapi multithreading

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

  1. 及时响应终止请求
  2. 泵消息
  3. 等待消息时不阻塞线程

我最初对消息泵的实现GetMessage如下:

while not Terminated and GetMessage(Msg, 0, 0, 0) do
begin
  TranslateMessage(Msg);
  DispatchMessage(Msg);
end;
Run Code Online (Sandbox Code Playgroud)

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

我的第二种实现方式(受此答案启发)曾经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;
Run Code Online (Sandbox Code Playgroud)

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

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

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

Rem*_*eau 5

我对消息泵的最初实现使用GetMessage,例如:

while not Terminated and GetMessage(Msg, 0, 0, 0) do
begin
  TranslateMessage(Msg);
  DispatchMessage(Msg);
end;
Run Code Online (Sandbox Code Playgroud)

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

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

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

我的第二种实现方式(受此答案启发)曾经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;
Run Code Online (Sandbox Code Playgroud)

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

SendMessage...()系列函数将直接传递消息到目标窗口的消息处理过程,完全绕过消息队列。所以,MsgWaitForMultipleObjects()(Get|Peek)Message()永远不会报告SENT从消息SendMessage...()中,只有一个发布从消息PostMessage()PostThreadMessage()(或合成的消息,比如WM_TIMERWM_PAINT等)。但是,在跨线程边界发送消息时,接收线程仍需要执行消息检索调用(is,(Get|Peek)Message()),以便将发送的消息实际传递到窗口过程。

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

在线程内部,最好使用等待计时器代替WM_TIMER,然后可以将计时器与一起使用MsgWaitForMultipleObjects()。但是,实际上,使用GetMessage()with WM_TIMERMsgWaitForMultipleObjects()with with timeout 之间几乎没有什么区别,因此无需浪费系统资源来创建计时器。