等待 Thread 实例启动的正确方法是什么
What is the proper way to wait for TThread instance to start up
在 TThread
实例创建和启动之间,主线程将继续执行代码。如果主线程中的代码依赖于有问题的线程完全启动并且 运行 它必须以某种方式等待直到线程 Execute
方法实际启动。
考虑以下代码:
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;
没有等待 IsRunning
标志的循环,DoAction
将无法成功将 post 消息发送到包含的 window 句柄,因为它尚未创建。 WndProc
里面的inc(Test)
基本不会触发
是否有更好的方法来等待线程启动并在 Execute
方法内完成必要的初始化,或者这个解决方案是否已经达到了最佳状态?
注意:我知道 AllocateHWnd
和 DeallocateHWnd
不是线程安全的,不应像上面的示例那样在生产代码中使用。
主线程
- 创建一个事件。例如,
TSimpleEvent
足以满足您的需求。
- 将事件设置为无信号。对于
TSimpleEvent
,这是对 ResetEvent
的调用。我希望新铸造的 TSimpleEvent
会处于非信号状态,但我想不起来那个细节了。
- 创建线程,在构造函数中传递事件。
- 等待事件发出信号。对于
TSimpleEvent
这意味着调用 WaitFor
.
工作线程
- 记下传递给线程构造函数的事件。
- 在线程执行开始时,发出事件信号。对于
TSimpleEvent
这意味着调用 SetEvent
.
正如 David 指出的那样,TEvent
将适用于此,任何数量的其他同步对象也是如此。例如(因为我基本上已经写完了):
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.
将 FIsRunning
从 Boolean
更改为 TEvent
,以便在一切准备就绪时收到信号。
现在您可以随时等待此事件(尤其是在像 DoAction
这样的 public 方法中):
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;
现在一切都非常容易使用,如果有特殊原因等待(访问DoAction
方法太快),您只需要等待
var
t: TWndThread;
begin
t := TWndThread.Create;
try
t.DoAction;
finally
t.Free;
end;
end;
我猜我明白你的意思了:
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;
否则您可以将 WaitForSingleObject
从 AfterConstruction
移动到您的 DoAction
代码,如 Rufo 爵士所提议:
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;
在 TThread
实例创建和启动之间,主线程将继续执行代码。如果主线程中的代码依赖于有问题的线程完全启动并且 运行 它必须以某种方式等待直到线程 Execute
方法实际启动。
考虑以下代码:
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;
没有等待 IsRunning
标志的循环,DoAction
将无法成功将 post 消息发送到包含的 window 句柄,因为它尚未创建。 WndProc
里面的inc(Test)
基本不会触发
是否有更好的方法来等待线程启动并在 Execute
方法内完成必要的初始化,或者这个解决方案是否已经达到了最佳状态?
注意:我知道 AllocateHWnd
和 DeallocateHWnd
不是线程安全的,不应像上面的示例那样在生产代码中使用。
主线程
- 创建一个事件。例如,
TSimpleEvent
足以满足您的需求。 - 将事件设置为无信号。对于
TSimpleEvent
,这是对ResetEvent
的调用。我希望新铸造的TSimpleEvent
会处于非信号状态,但我想不起来那个细节了。 - 创建线程,在构造函数中传递事件。
- 等待事件发出信号。对于
TSimpleEvent
这意味着调用WaitFor
.
工作线程
- 记下传递给线程构造函数的事件。
- 在线程执行开始时,发出事件信号。对于
TSimpleEvent
这意味着调用SetEvent
.
正如 David 指出的那样,TEvent
将适用于此,任何数量的其他同步对象也是如此。例如(因为我基本上已经写完了):
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.
将 FIsRunning
从 Boolean
更改为 TEvent
,以便在一切准备就绪时收到信号。
现在您可以随时等待此事件(尤其是在像 DoAction
这样的 public 方法中):
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;
现在一切都非常容易使用,如果有特殊原因等待(访问DoAction
方法太快),您只需要等待
var
t: TWndThread;
begin
t := TWndThread.Create;
try
t.DoAction;
finally
t.Free;
end;
end;
我猜我明白你的意思了:
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;
否则您可以将 WaitForSingleObject
从 AfterConstruction
移动到您的 DoAction
代码,如 Rufo 爵士所提议:
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;