如何退出线程的消息循环? [英] How to exit a thread's message loop?

查看:181
本文介绍了如何退出线程的消息循环?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



背景



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

  procedure ThreadProcedure; 
var
msg:TMsg;
begin
//调用PeekMessage强制系统创建消息队列。
PeekMessage(msg,NULL,WM_USER,WM_USER,PM_NOREMOVE);
结束

现在外界可以向我们的帖子发帖:

  PostThreadMessage(nThreadID,WM_ReadyATractorBeam,0,0); 

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

  procedure ThreadProcedure; 
var
msg:TMsg;
begin
//调用PeekMessage强制系统创建消息队列。
PeekMessage(msg,NULL,WM_USER,WM_USER,PM_NOREMOVE);

//开始我们的消息抽取循环。
// GetMessage将收到一个WM_QUIT

时返回false //如果有一个错误,GetMessage可以返回-1
// Delphi LongBool将非零解释为true。
//如果GetMessage *执行*失败,则msg将无效。
//我们想要一些方法来处理它。
//无效:
// 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;

//如果没有关联窗口,则无法调用Translate / Dispatch。
//调度将抛出消息
// else
// TranslateMessage(Msg);
// DispatchMessage(Msg);
// end;
结束
结束

我的问题是使用 GetMessage 收到 WM_QUIT 消息并返回false。



我们已经学会了 不正确的方式发布 WM_QUIT 消息是调用:

  PostThreadMessage(nThreadId,WM_QUIT,0,0 ); 

甚至有一个在那里人们采用这种方法的例子。从MSDN:


不要使用 WM_QUIT .microsoft.com / en-us / library / windows / desktop / ms644944%28v = vs85%29.aspxrel =noreferrer> PostMessage 功能;请使用 PostQuitMessage


正确的方式是某人调用 PostQuitMessage PostQuitMessage 是一个特殊功能,它设置与消息队列相关联的特殊标志,以便 GetMessage 将合成一个 WM_QUIT 消息时间是正确的。



如果这是与窗口相关联的消息循环,那么标准设计模式当窗口被破坏时,收到一个 WM_DESTROY 消息,我们抓住它并调用 PostQuitMessage

  procedure ThreadProcedure; 
var
msg:TMsg;
begin
//调用PeekMessage强制系统创建消息队列。
PeekMessage(msg,NULL,WM_USER,WM_USER,PM_NOREMOVE);

//开始我们的消息抽取循环。
//当GetMessage收到一个WM_QUIT
时,GetMessage将返回false,而Longint(GetMessage(msg,0,0,0))> 0 do
begin
case msg.message of
WM_ReadyATractorBeam:ReadyTractorBeam;
WM_DESTROY:PostQuitMessage(0);
结束
结束
结束

问题是 WM_DESTROY 由窗口管理器发送。当有人调用 DestroyWindow 时发送。刚刚发布 WM_DESTROY 是错误的。



现在我可以合成一些人造的 WM_PleaseEndYourself 消息:

  PostThreadMessage(nThreadID,WM_PleaseEndYourself,0 ,0); 

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

  procedure ThreadProcedure; 
var
msg:TMsg;
begin
//调用PeekMessage强制系统创建消息队列。
PeekMessage(msg,NULL,WM_USER,WM_USER,PM_NOREMOVE);

//开始我们的消息抽取循环。
//当GetMessage收到一个WM_QUIT
时,GetMessage将返回false,而Longint(GetMessage(msg,0,0,0))> 0 do
begin
case msg.message of
WM_ReadyATractorBeam:ReadyTractorBeam;
WM_PleaseEndYourself:PostQuitMessage(0);
结束
结束
结束

但是有没有一种可以退出线程的消息循环的 / p>




但不要这样做



事实证明对于 不要使用无窗口消息队列更好。如果你没有一个窗口发送消息,很多事情可能是无意的,如果没有窗口。



而是分配隐藏窗口(例如使用Delphi的线程 - 不安全 AllocateHwnd ),并使用简单的旧版 PostMessage

 程序TMyThread.Execute; 
var
msg:TMsg;
begin
Fhwnd:= AllocateHwnd(WindowProc);
如果Fhwnd = 0,则退出;
尝试
,而Longint(GetMessage(msg,0,0,0))> 0 do
begin
TranslateMessage(msg);
DispatchMessage(msg);
结束
finally
DeallocateHwnd(Fhwnd);
Fhwnd:= 0;
结束
结束

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

  WM_TerminateYourself = WM_APP + 1; 

程序TMyThread.WindowProc(var msg:TMessage);
begin
case msg.Msg
WM_ReadyATractorBeam:ReadyTractorBeam;
WM_TerminateYourself:PostQuitMessage(0);
else
msg.Result:= DefWindowProc(Fhwnd,msg.msg,msg.wParam,msg.lParam);
结束
结束

当您希望线程完成时,您会告诉它:

 程序TMyThread.Terminate; 
begin
PostMessage(Fhwnd,WM_TerminateYourself,0,0);
结束


解决方案

没有规范的方式;没有教规您通过发布 wm_Quit 消息获取 GetMessage 返回零,然后通过调用 PostQuitMessage 。什么时候和你怎么知道这样做取决于你。



常见的做法是回应 wm_Destroy ,但这只是因为退出程序的常见方法是关闭窗口。如果没有关闭窗口,请选择不同的方式。您的 wm_PleaseEndYourself 想法很好。



您甚至不必发送消息。您可以使用像事件或信号量这样的等待对象,然后使用 MsgWaitForMultipleObjects 来检测是否在等待新消息时发出信号。



您甚至不必等待 GetMessage 返回零。如果你已经知道线程需要停止,那么你可以简单地停止处理消息。有很多方法可以退出循环。您可以使用退出 break raise ,甚至 goto ,或者您可以设置一个标记,检查循环条件以及 GetMessage 的返回值。 / p>

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


A background-thread can be configured to receive window messages. You would post messages to the thread using PostThreadMessage. What's the correct way to exit that message loop?

Background

Before you can post messages to a background thread, the thread needs to ensure that a message queue is created by calling 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;

Now the outside world is able to post messages to our thread:

PostThreadMessage(nThreadID, WM_ReadyATractorBeam, 0, 0);

and our thread sits in a GetMessage loop:

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;

My question is what's the correct way to have GetMessage receive a WM_QUIT message and return false.

We've already learned that the incorrect way to post a WM_QUIT message is to call:

PostThreadMessage(nThreadId, WM_QUIT, 0, 0);

There is even examples out there where people employ this approach. From MSDN:

Do not post the WM_QUIT message using the PostMessage function; use PostQuitMessage.

The correct way is for "someone" to call PostQuitMessage. PostQuitMessage is a special function, that sets the special flag associated with a message queue so that GetMessage will synthesize a WM_QUIT message when the time is right.

If this were a message loop associated with a "window", then the standard design pattern is when the window is being destroyed, and a WM_DESTROY message is received, we catch it and call 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;

Problem is that WM_DESTROY is sent by the Window Manager. It is sent when someone calls DestroyWindow. It is wrong to just post WM_DESTROY.

Now i could synthesize some artifical WM_PleaseEndYourself message:

PostThreadMessage(nThreadID, WM_PleaseEndYourself, 0, 0);

and then handle it in my thread's message loop:

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;

But is there a canonical way to exit a thread's message loop?


But don't do this

It turns out that it's better for everyone that you don't use a windowless message queue. A lot of things can be unintentionally, and subtly, broken if you don't have a window for messages to be dispatched to.

Instead allocate hidden window (e.g. using Delphi's thread-unsafe AllocateHwnd) and post messages to it using plain 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;

Where we can have a plain old window procedure to handle the messages:

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;    

and when you want the thread to finish, you tell it:

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

解决方案

There is no canonical way; there is no canon. You get GetMessage to return zero by posting a wm_Quit message, and you do that by calling PostQuitMessage. When and how you know to do that is up to you.

It common to do it in response to wm_Destroy, but that's only because the common way to exit programs is by closing windows. If you have no window to close, then choose a different way. Your wm_PleaseEndYourself idea is fine.

You don't even have to send a message. You could use some waitable object like an event or a semaphore, and then use MsgWaitForMultipleObjects to detect whether it's signaled while also waiting for new messages.

You don't really even have to wait for GetMessage to return zero. If you already know the thread needs to stop, then you can simply stop processing messages entirely. There are lots of ways to exit a loop. You could use exit, break, raise, or even goto, or you could set a flag that you check in the loop condition along with the return value of GetMessage.

Note also that GetMessage returns -1 on failure, which as a non-zero value will be interpreted as true. You probably don't want to continue your message loop if GetMessage fails, so instead check for GetMessage(...) > 0, or do like the documentation recommends.

这篇关于如何退出线程的消息循环?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆