[英]What is the proper way to wait for TThread instance to start up
Between time of TThread
instance creation and start up, main thread will continue with code execution. 在
TThread
实例创建和启动之间,主线程将继续执行代码。 If code in main thread depends on thread in question to be fully up and running it has to wait somehow until thread Execute
method actually started. 如果主线程中的代码依赖于有问题的线程完全启动并运行,它必须以某种方式等待,直到线程
Execute
方法实际启动。
Consider following code: 考虑以下代码:
const
WM_MY_ACTION = WM_APP + 10;
type
TWndThread = class(TThread)
protected
fWndHandle: THandle;
IsRunning: boolean;
procedure WndProc(var Msg: TMessage);
procedure Execute; override;
public
Test: integer;
procedure AfterConstruction; override;
procedure DoAction;
end;
procedure TWndThread.AfterConstruction;
begin
inherited;
while not IsRunning do Sleep(100); // wait for thread start up
end;
procedure TWndThread.Execute;
var
Msg: TMsg;
begin
fWndHandle := AllocateHWnd(WndProc);
IsRunning := true;
try
while not Terminated do
begin
if MsgWaitForMultipleObjects(0, nil^, False, 1000, QS_ALLINPUT) = WAIT_OBJECT_0 then
begin
while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
end;
end;
finally
DeallocateHWnd(fWndHandle);
end;
end;
procedure TWndThread.WndProc(var Msg: TMessage);
begin
case Msg.Msg of
WM_MY_ACTION:
begin
inc(Test);
end;
else Msg.Result := DefWindowProc(fWndHandle, Msg.Msg, Msg.WParam, Msg.LParam);
end;
end;
procedure TWndThread.DoAction;
begin
PostMessage(fWndHandle, WM_MY_ACTION, 0, 0);
end;
var
t: TWndThread;
begin
t := TWndThread.Create;
t.DoAction;
t.Terminate;
end;
Without loop that waits for IsRunning
flag, DoAction
will not be able to successfully post message to contained window handle because it will not yet be created. 如果没有等待
IsRunning
标志的循环, DoAction
将无法成功将消息发布到包含的窗口句柄,因为它尚未创建。 Basically, inc(Test)
inside WndProc
will not be triggered. 基本上,
WndProc
inc(Test)
不会被触发。
Is there a better way to wait for thread start up and complete necessary initialization inside Execute
method or is this solution as good as it gets? 有没有更好的方法等待线程启动并在
Execute
方法中完成必要的初始化,或者这个解决方案是否得到了好处?
Note: I am aware that AllocateHWnd
and DeallocateHWnd
are not thread safe and should not be used in production code like above example. 注意:我知道
AllocateHWnd
和DeallocateHWnd
不是线程安全的,不应该像上面的例子那样在生产代码中使用。
Main thread 主线程
TSimpleEvent
will suffice for your needs. TSimpleEvent
就足以满足您的需求。 TSimpleEvent
that's a call to ResetEvent
. TSimpleEvent
这是一个呼叫ResetEvent
。 I expect that a newly minted TSimpleEvent
would be in the non-signaled state, but off the top of my head I cannot remember that detail. TSimpleEvent
会处于无信号状态,但是我无法记住这个细节。 TSimpleEvent
that means calling WaitFor
. TSimpleEvent
,这意味着调用WaitFor
。 Worker thread 工人线程
TSimpleEvent
that means calling SetEvent
. TSimpleEvent
,这意味着调用SetEvent
。 Change FIsRunning
from Boolean
to TEvent
to get signaled if everything is ready to use. 将
FIsRunning
从Boolean
更改为TEvent
,以便在所有内容都可以使用时发出信号。
Now you can wait for this event at any point (especially in the public methods like DoAction
): 现在您可以随时等待此事件(特别是在
DoAction
等公共方法中):
const
WM_MY_ACTION = WM_APP + 10;
type
TWndThread = class(TThread)
private
FIsRunning: TEvent; // <- event
protected
fWndHandle: THandle;
procedure WndProc(var Msg: TMessage);
procedure Execute; override;
procedure CheckIsRunning; // guard method
public
constructor Create;
destructor Destroy; override;
procedure DoAction;
end;
constructor TWndThread.Create;
begin
// create event
FIsRunning := TEvent.Create( nil, True, False, '' );
inherited;
end;
destructor Destroy;
begin
inherited;
// free event
FIsRunning.Free;
end;
procedure CheckIsRunning;
begin
// guard if terminated
if Terminated then
raise Exception.Create( 'Already terminated' );
// wait for event
FIsRunning.WaitFor();
end;
procedure TWndThread.Execute;
var
Msg: TMsg;
begin
fWndHandle := AllocateHWnd(WndProc);
// set event
FIsRunning.SetEvent;
try
while not Terminated do
begin
if MsgWaitForMultipleObjects(0, nil^, False, 1000, QS_ALLINPUT) = WAIT_OBJECT_0 then
begin
while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
end;
end;
finally
DeallocateHWnd(fWndHandle);
end;
end;
procedure TWndThread.WndProc(var Msg: TMessage);
begin
case Msg.Msg of
WM_MY_ACTION:
begin
inc(Test);
end;
else Msg.Result := DefWindowProc(fWndHandle, Msg.Msg, Msg.WParam, Msg.LParam);
end;
end;
procedure TWndThread.DoAction;
begin
// guard method
CheckIsRunning;
// do the action
PostMessage(fWndHandle, WM_MY_ACTION, 0, 0);
end;
Now everything is very easy to use and you only have to wait, if there is a special reason for waiting (accessing the DoAction
method too fast) 现在一切都很容易使用,你只需等待,如果有特殊原因等待(访问
DoAction
方法太快)
var
t: TWndThread;
begin
t := TWndThread.Create;
try
t.DoAction;
finally
t.Free;
end;
end;
As David noted, a TEvent
would work for this, as would any number of other synchronization objects. 正如大卫所指出的那样,
TEvent
会为此工作,就像任何其他同步对象一样。 As example (since I was mostly done writing it anyway) : 作为例子(因为我大部分时间都写完了):
program Project1;
{$APPTYPE CONSOLE}
uses
Classes, SysUtils, SyncObjs;
type
TMyThread = class(TThread)
private
FWaitEvent : TEvent;
public
constructor Create(AWaitEvent : TEvent);
procedure Execute; override;
property WaitEvent : TEvent read FWaitEvent;
end;
constructor TmyThread.Create(AWaitEvent: TEvent);
begin
inherited Create(true);
FWaitEvent := AWaitEvent;
end;
procedure TMyThread.Execute;
begin
// maybe do something
sleep(1000);
FWaitEvent.SetEvent;
// do more things
end;
var
LMyThread : TMyThread;
LWaitEvent : TEvent;
LWaitResult : TWaitResult;
begin
LWaitEvent := TEvent.Create;
LMyThread := TMyThread.Create(LWaitEvent);
try
LMyThread.Start;
WriteLn('Created Thread...');
LWaitResult := LMyThread.WaitEvent.WaitFor(5000);
case LWaitResult of
wrSignaled : WriteLn('Waited successfully for thread start');
wrTimeout : WriteLn('Timeout waiting for thread');
wrAbandoned : WriteLn('Object freed already.');
wrError : WriteLn('Wait error'); // check LastError
wrIOCompletion : // undocumented?
end;
finally
LMyThread.WaitFor;
LMyThread.Free;
LWaitEvent.Free;
end;
ReadLn;
end.
Guess I got your idea: 猜猜我有你的想法:
uses
Windows, SysUtils, Classes;
type
TMyThread = class(TThread)
private
FStartEvent: THandle;
protected
procedure Execute; override;
public
procedure AfterConstruction; override;
end;
implementation
{ TMyThread }
procedure TMyThread.AfterConstruction;
begin
FStartEvent:= CreateEvent(nil, True, False, nil);
inherited; // this starts the thread
if WaitForSingleObject(FStartEvent, INFINITE) = WAIT_FAILED
// means the thread finished;
// should not happen but you can check it to be safe
then ..;
// otherwise nothing should be done
end;
procedure TMyThread.Execute;
begin
SetEvent(FStartEvent);
// ...
CloseHandle(FStartEvent);
end;
or else you can move WaitForSingleObject
from AfterConstruction
to your DoAction
code as Sir Rufo proposed: 或者你可以像
DoAction
Rufo建议的那样将WaitForSingleObject
从AfterConstruction
移动到你的DoAction
代码:
procedure TMyThread.CheckRunning;
begin
if WaitForSingleObject(FStartEvent, INFINITE) = WAIT_FAILED
then // the thread already finished;
// this should not normally happen,
// maybe application is terminating or something else unexpected.
// ..;
// else the thread is up and running here.
end;
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.