【发布时间】:2012-05-14 03:47:16
【问题描述】:
后台线程可以配置为接收窗口消息。您可以使用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 消息并返回 false 的正确方法是什么。
我们已经知道that the incorrect way to post a WM_QUIT message 是调用:
PostThreadMessage(nThreadId, WM_QUIT, 0, 0);
甚至还有examples out there where people employ this approach。来自 MSDN:
不要使用PostMessage 函数发布WM_QUIT 消息;使用PostQuitMessage。
正确的方式是“某人”拨打PostQuitMessage。 PostQuitMessage is a special function,设置与消息队列关联的特殊标志,以便GetMessage 在适当的时候合成WM_QUIT 消息。
如果这是一个与“window”相关联的消息循环,那么标准设计模式是当窗口被销毁并收到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_DESTROYis sent by the Window Manager. It is sent when someone calls DestroyWindow. It is wrong to just post 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;
但是有没有规范的方法可以退出线程的消息循环?
但不要这样做
事实证明,每个人最好不要使用无窗口消息队列。如果您没有发送消息的窗口,很多事情可能会在无意中被巧妙地破坏。
Instead allocate hidden window(例如,使用 Delphi 的 thread-unsafe 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;
我们可以有一个普通的旧窗口过程来处理消息:
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;
【问题讨论】:
-
一旦你有一个特殊的消息,为什么还要麻烦 PostQuitMessage?离开那里。
-
如果线程中没有创建窗口,调用
TranslateMessage-DispatchMessage的目的是什么? -
@Dialectus break 会更容易!
-
不像加州酒店! ;-)
-
@Sertac 正确,documentation 明确表示调用
GetMessage会创建消息队列。
标签: multithreading delphi winapi message-loop