3

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

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

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

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

我发现的问题是,除非有消息,否则 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;

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

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

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

4

1 回答 1

5

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

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

我发现的问题是,除非有消息,否则 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;

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

SendMessage...()系列函数将直接向目标窗口的消息过程传递消息,完全绕过消息队列。因此MsgWaitForMultipleObjects(),并且(Get|Peek)Message()永远不会报告来自 的已发送消息SendMessage...(),只会报告来自or的已发布消息(或合成消息,如、等)。但是,当跨线程边界发送消息时,接收线程仍然需要执行消息检索调用 (is, ),以便将发送的消息实际传递到窗口过程。PostMessage()PostThreadMessage()WM_TIMERWM_PAINT(Get|Peek)Message()

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

在线程内部,最好使用可等待计时器而不是WM_TIMER,然后您可以将计时器与MsgWaitForMultipleObjects(). GetMessage()但实际上,使用withWM_TIMER与使用超时几乎没有什么区别MsgWaitForMultipleObjects(),因此没有必要浪费系统资源来创建计时器。

于 2019-11-27T19:17:10.273 回答