在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,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); end; var t: TWndThread; begin t := TWndThread.Create; t.DoAction; t.Terminate; end;
如果没有等待IsRunning标志的循环,DoAction将无法成功将消息发布到包含的窗口句柄,因为它尚未创建.基本上,WndProc中的inc(Test)不会被触发.
有没有更好的方法等待线程启动并在Execute方法中完成必要的初始化,或者这个解决方案是否得到了好处?
注意:我知道AllocateHWnd和DeallocateHWnd不是线程安全的,不应该像上面的例子那样在生产代码中使用.
解决方法
将FIsRunning从布尔值更改为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,'' ); 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,Msg.LParam); end; end; procedure TWndThread.DoAction; begin // guard method CheckIsRunning; // do the action PostMessage(fWndHandle,0); end;
现在一切都很容易使用,你只需等待,如果有特殊原因等待(访问DoAction方法太快)
var t: TWndThread; begin t := TWndThread.Create; try t.DoAction; finally t.Free; end; end;