在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
将无法成功将消息发布到包含的窗口句柄,因为它尚未创建。基本上,inc(Test)
内的WndProc
将不会被触发。
有没有更好的方法可以等待线程启动并在Execute
方法中完成必要的初始化,或者这个解决方案是否能够得到好处?
注意:我知道AllocateHWnd
和DeallocateHWnd
不是线程安全的,不应该像上面的示例那样在生产代码中使用。
答案 0 :(得分:9)
主要帖子
TSimpleEvent
就足以满足您的需求。TSimpleEvent
的{{1}}。ResetEvent
。我希望新铸造的TSimpleEvent
处于无信号状态,但是我不记得那个细节。TSimpleEvent
表示调用WaitFor
。工作线程
TSimpleEvent
表示调用SetEvent
。答案 1 :(得分:8)
将FIsRunning
从Boolean
更改为TEvent
,以便在一切准备就绪时发出信号。
现在您可以随时等待此事件(特别是在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;
现在一切都很容易使用,你只需等待,如果有特殊原因需要等待(快速访问DoAction
方法)
var
t: TWndThread;
begin
t := TWndThread.Create;
try
t.DoAction;
finally
t.Free;
end;
end;
答案 2 :(得分:2)
正如大卫所指出的那样,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.
答案 3 :(得分:0)
猜猜我明白了:
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;
或者你可以像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;