What is the correct way to wait for a TThread instance to start

Between the time the TThread instance was created and started, the main thread will continue to execute the code. If the code in the main thread depends on the current thread, which should be fully started, it must wait until the Execute process begins.

Consider the 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 a loop waiting for the IsRunning flag, DoAction will not be able to successfully send the message to the contained window handle because it has not yet been created. Basically, inc(Test) inside WndProc will not start.

Is there a better way to wait for the thread to start and complete the necessary initialization inside the Execute method, or is this solution as good as it gets?

Note. I know that AllocateHWnd and DeallocateHWnd are not thread safe and should not be used in production code, as shown above.

+5
source share
4 answers

Change FIsRunning from Boolean to TEvent to get a signal if everything is ready to use.

Now you can wait for this event at any time (especially in public methods such as 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 can only wait if there is a special reason for waiting (quick access to the DoAction method)

 var t: TWndThread; begin t := TWndThread.Create; try t.DoAction; finally t.Free; end; end; 
+7
source

Main stream

  • Create an event. For example, TSimpleEvent will be sufficient for your needs.
  • Set an event without an alarm. For TSimpleEvent to call ResetEvent . I expect the new TSimpleEvent be able to without alarm, but from my head I canโ€™t remember this detail.
  • Create a stream by passing an event in the constructor.
  • Wait for the event to signal. For TSimpleEvent this means calling WaitFor .

Work flow

  • Record the event passed to the stream constructor.
  • Report the event at the start of the thread. For TSimpleEvent this means calling SetEvent .
+8
source

As David noted, TEvent will work for this, as well as any number of other synchronization objects. As an example (since I basically wrote it generally):

 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. 
+2
source

Guess I have 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; 

otherwise you can move WaitForSingleObject from AfterConstruction to DoAction code, as Sir Rufo suggested:

 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; 
0
source

Source: https://habr.com/ru/post/1213724/


All Articles