6

Between time of TThread instance creation and start up, main thread will continue with code execution. 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.

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. Basically, inc(Test) inside WndProc will not be triggered.

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?

Note: I am aware that AllocateHWnd and DeallocateHWnd are not thread safe and should not be used in production code like above example.

Dalija Prasnikar
  • 27,212
  • 44
  • 82
  • 159

4 Answers4

9

Main thread

  1. Create an event. For instance, TSimpleEvent will suffice for your needs.
  2. Set the event to be non-signaled. For TSimpleEvent that's a call to 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.
  3. Create the thread, passing the event in the constructor.
  4. Wait for the event to become signaled. For TSimpleEvent that means calling WaitFor.

Worker thread

  1. Make a note of the event passed to the thread's constructor.
  2. At the start of the thread execution, signal the event. For TSimpleEvent that means calling SetEvent.
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • I edited question - moved wait code to `AfterConstruction` to make thread class more self contained. I presume that nothing would prevent me from using `TSimpleEvent` in contained manner, too? – Dalija Prasnikar Feb 19 '15 at 14:21
  • 3
    The perfect place for waiting is the `DoAction` method or any other public method where you have to ensure everything is running – Sir Rufo Feb 19 '15 at 15:25
  • 1
    @Sir Rufo is correct that it is better to wait only when you need to wait – David Heffernan Feb 19 '15 at 20:39
8

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

Now you can wait for this event at any point (especially in the public methods like 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)

var
  t: TWndThread;
begin
  t := TWndThread.Create;
  try
    t.DoAction;
  finally
    t.Free;
  end;
end;
Sir Rufo
  • 18,395
  • 2
  • 39
  • 73
2

As David noted, a TEvent would work for this, as would any number of other synchronization objects. 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.
J...
  • 30,968
  • 6
  • 66
  • 143
0

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:

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;
kludg
  • 27,213
  • 5
  • 67
  • 118
  • 1
    Just think about if every thread takes 2 seconds to get up running. Just creating 10 threads will block for 20 seconds. Look at my answer, there is no waiting until calling the action – Sir Rufo Feb 19 '15 at 16:59