如何退出线程的消息循环?

Ian*_*oyd 13 delphi winapi multithreading message-loop

后台线程可以配置为接收窗口消息.您可以使用发布消息到线程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;
Run Code Online (Sandbox Code Playgroud)

现在外面的世界能够将消息发布到我们的线程:

PostThreadMessage(nThreadID, WM_ReadyATractorBeam, 0, 0);
Run Code Online (Sandbox Code Playgroud)

我们的线程处于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;
Run Code Online (Sandbox Code Playgroud)

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

我们已经知道发布消息错误方法WM_QUIT是调用:

PostThreadMessage(nThreadId, WM_QUIT, 0, 0);
Run Code Online (Sandbox Code Playgroud)

甚至有人使用这种方法的例子.来自MSDN:

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

正确的方法是"人"来称呼PostQuitMessage.PostQuitMessage 是一个特殊函数,它设置与消息队列关联的特殊标志,以便在时间合适时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;
Run Code Online (Sandbox Code Playgroud)

问题是WM_DESTROY 由Window Manager发送的.当有人打电话时发送DestroyWindow.发帖是错误的WM_DESTROY.

现在我可以综合一些人为的WM_PleaseEndYourself信息:

PostThreadMessage(nThreadID, WM_PleaseEndYourself, 0, 0);
Run Code Online (Sandbox Code Playgroud)

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

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;
Run Code Online (Sandbox Code Playgroud)

但是有一种规范的方法来退出线程的消息循环吗?


但是不要这样做

事实证明,对于每个人来说,使用无窗口消息队列会更好.如果你没有一个可以发送消息的窗口,很多事情可能是无意的,并且巧妙地破坏了.

而是分配隐藏窗口(例如使用Delphi的线程 - 不安全 AllocateHwnd)并使用普通旧邮件发送消息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;
Run Code Online (Sandbox Code Playgroud)

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

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;    
Run Code Online (Sandbox Code Playgroud)

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

procedure TMyThread.Terminate;
begin
   PostMessage(Fhwnd, WM_TerminateYourself, 0, 0);
end;
Run Code Online (Sandbox Code Playgroud)

Rob*_*edy 7

没有规范的方式; 没有佳能.您可以GetMessage通过发布wm_Quit消息返回零,然后通过调用来执行此操作PostQuitMessage.您何时以及如何知道这一点取决于您.

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

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

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

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


Dav*_*nan 5

使用PostThreadMessage不一定是不正确的.你联系的Raymond的文章说:

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

如果这里列出的担忧并不适用于你的消息队列,然后调用PostThreadMessageWM_QUIT和自己敲出来.否则,您将需要创建一个特殊信号,即用户定义的消息,允许您PostQuitMessage从线程调用.