14

后台线程可以配置为接收窗口消息。您将使用PostThreadMessage. 退出该消息循环的正确方法是什么?

背景

在您可以将消息发布到后台线程之前,该线程需要确保通过调用创建消息队列PeekMessage

procedure ThreadProcedure;
var
   msg: TMsg;
begin
   //Call PeekMessage to force the system to create the message queue.
   PeekMessage(msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
end;

现在外界可以向我们的线程发布消息:

PostThreadMessage(nThreadID, WM_ReadyATractorBeam, 0, 0);

我们的线程位于一个GetMessage循环中:

procedure ThreadProcedure;
var
   msg: TMsg;
begin
   //Call PeekMessage to force the system to create the message queue.
   PeekMessage(msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);

   //Start our message pumping loop. 
   //GetMessage will return false when it receives a WM_QUIT

   //   GetMessage can return -1 if there's an error
   //   Delphi LongBool interprets non-zero as true.
   //   If GetMessage *does* fail, then msg will not be valid. 
   //   We want some way to handle that.
   //Invalid:
   //while (GetMessage(msg, 0, 0, 0)) do
   //Better:
   while LongInt(GetMessage(msg, 0, 0, 0)) > 0 do
   begin
      case msg.message of
      WM_ReadyATractorBeam: ReadyTractorBeam;

      // No point in calling Translate/Dispatch if there's no window associated.
      // Dispatch will just throw the message away
//    else
//       TranslateMessage(Msg);
//       DispatchMessage(Msg);
//    end;
   end;
end;

GetMessage我的问题是接收WM_QUIT消息并返回错误的正确方法是什么。

我们已经了解到,发布消息错误方式WM_QUIT是调用:

PostThreadMessage(nThreadId, WM_QUIT, 0, 0);

甚至还有一些人们采用这种方法的例子。来自 MSDN:

不要使用PostMessage函数发布WM_QUIT消息;使用PostQuitMessage

正确的方法是让“某人”打电话PostQuitMessagePostQuitMessage 是一个特殊函数,它设置与消息队列关联的特殊标志,以便在适当的时候GetMessage合成消息。WM_QUIT

如果这是一个与“窗口”相关联的消息循环,那么标准设计模式是当窗口被销毁并WM_DESTROY接收到消息时,我们捕获它并调用PostQuitMessage

procedure ThreadProcedure;
var
   msg: TMsg;
begin
   //Call PeekMessage to force the system to create the message queue.
   PeekMessage(msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);

   //Start our message pumping loop. 
   //GetMessage will return false when it receives a WM_QUIT
   while Longint(GetMessage(msg, 0, 0, 0)) > 0 do
   begin
      case msg.message of
      WM_ReadyATractorBeam: ReadyTractorBeam;
      WM_DESTROY: PostQuitMessage(0);
      end;
   end;
end;

问题是WM_DESTROY 由窗口管理器发送的。当有人来电时发送DestroyWindow。只发帖是错误的WM_DESTROY

现在我可以合成一些人工WM_PleaseEndYourself信息:

PostThreadMessage(nThreadID, WM_PleaseEndYourself, 0, 0);

然后在我的线程的消息循环中处理它:

procedure ThreadProcedure;
var
   msg: TMsg;
begin
   //Call PeekMessage to force the system to create the message queue.
   PeekMessage(msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);

   //Start our message pumping loop. 
   //GetMessage will return false when it receives a WM_QUIT
   while Longint(GetMessage(msg, 0, 0, 0)) > 0 do
   begin
      case msg.message of
      WM_ReadyATractorBeam: ReadyTractorBeam;
      WM_PleaseEndYourself: PostQuitMessage(0);
      end;
   end;
end;

但是有没有一种规范的方法可以退出线程的消息循环?


但不要这样做

事实证明,不使用无窗口消息队列对每个更好。如果您没有发送消息的窗口,很多事情可能会在无意中被巧妙地破坏。

而是分配隐藏窗口(例如,使用 Delphi 的线程- unsafe AllocateHwnd)并使用普通的 old 向其发布消息PostMessage

procedure TMyThread.Execute;
var
   msg: TMsg;
begin
   Fhwnd := AllocateHwnd(WindowProc);
   if Fhwnd = 0 then Exit;
   try
      while Longint(GetMessage(msg, 0, 0, 0)) > 0 do
      begin
         TranslateMessage(msg);
         DispatchMessage(msg);
      end;
   finally
      DeallocateHwnd(Fhwnd);
      Fhwnd := 0;
   end;
end;

我们可以有一个普通的旧窗口过程来处理消息:

WM_TerminateYourself = WM_APP + 1;

procedure TMyThread.WindowProc(var msg: TMessage);
begin
   case msg.Msg of
   WM_ReadyATractorBeam: ReadyTractorBeam;
   WM_TerminateYourself: PostQuitMessage(0);
   else
      msg.Result := DefWindowProc(Fhwnd, msg.msg, msg.wParam, msg.lParam);
   end;
end;    

当你想让线程完成时,你告诉它:

procedure TMyThread.Terminate;
begin
   PostMessage(Fhwnd, WM_TerminateYourself, 0, 0);
end;
4

2 回答 2

7

没有规范的方法;没有佳能。您可以GetMessage通过发布消息返回零wm_Quit,然后调用PostQuitMessage. 您何时以及如何知道这样做取决于您。

响应 时这样做很常见wm_Destroy,但这只是因为退出程序的常用方法是关闭窗口。如果您没有要关闭的窗口,请选择其他方式。你的wm_PleaseEndYourself想法很好。

您甚至不必发送消息。您可以使用一些可等待的对象,例如事件或信号量,然后MsgWaitForMultipleObjects在等待新消息的同时检测它是否已发出信号。

您甚至不必等待GetMessage返回零。如果您已经知道线程需要停止,那么您可以完全停止处理消息。有很多方法可以退出循环。您可以使用exit, break, raise, 甚至goto, 或者您可以设置一个标志,您可以在循环条件中检查该标志以及 . 的返回值GetMessage

另请注意,GetMessage失败时返回 -1,作为非零值将被解释为 true。如果失败,您可能不想继续您的消息循环GetMessage,因此请检查GetMessage(...) > 0,或按照文档建议的方式进行操作。

于 2012-05-04T16:10:57.370 回答
6

使用PostThreadMessage不一定是错误的。您链接到的雷蒙德的文章说:

因为系统试图在“糟糕的时间”不注入 WM_QUIT 消息;相反,它在生成 WM_QUIT 消息之前等待事情“稳定下来”,从而减少程序可能处于由一系列发布消息触发的多步骤过程中间的机会。

如果此处概述的问题不适用于您的消息队列,那么请致电PostThreadMessageWM_QUIT淘汰自己。否则,您将需要创建一个特殊信号,即用户定义的消息,它允许您PostQuitMessage从线程中调用。

于 2012-05-04T16:11:50.053 回答