2015-02-19 9 views
5

Tra la creazione e l'avvio dell'istanza TThread, il thread principale continuerà con l'esecuzione del codice. Se il codice nel thread principale dipende dal thread in questione per essere completamente attivo e in esecuzione, esso deve attendere in qualche modo fino a quando il metodo del thread Execute non viene effettivamente avviato.Qual è il modo corretto di attendere l'avvio dell'istanza TThread

Considerate seguente codice:

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; 

Senza ciclo che attende IsRunning bandiera, DoAction non sarà in grado di inviare con successo un messaggio di handle di finestra contenuta perché non sarà ancora essere creato. Fondamentalmente, inc(Test) all'interno di WndProc non verrà attivato.

Esiste un modo migliore per attendere l'avvio del thread e completare l'inizializzazione necessaria all'interno del metodo Execute oppure questa soluzione è buona?

Nota: sono consapevole del fatto che AllocateHWnd e DeallocateHWnd non sono thread-safe e non devono essere utilizzati nel codice di produzione come nell'esempio precedente.

risposta

7

Variazione FIsRunningBoolean-TEvent per ottenere segnalato se tutto è pronto per l'uso.

Ora si può attendere per questo evento in qualsiasi punto (soprattutto nei metodi pubblici come 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; 

ora tutto è molto facile da usare e hai solo aspettare, se ci è un motivo speciale per l'attesa (l'accesso al metodo di DoAction troppo veloce)

var 
    t: TWndThread; 
begin 
    t := TWndThread.Create; 
    try 
    t.DoAction; 
    finally 
    t.Free; 
    end; 
end; 
8

thread principale

  1. creare un evento. Ad esempio, TSimpleEvent sarà sufficiente per le tue esigenze.
  2. Imposta l'evento per non essere segnalato. Per TSimpleEvent è una chiamata a ResetEvent. Mi aspetto che un nuovo TSimpleEvent sia nello stato non segnalato, ma dalla parte superiore della mia testa non riesco a ricordare quel dettaglio.
  3. Creare il thread, passando l'evento nel costruttore.
  4. Attendere che l'evento venga segnalato. Per TSimpleEvent significa chiamare WaitFor.

Worker filo

  1. Annotare l'evento passato al costruttore del thread.
  2. All'inizio dell'esecuzione del thread, segnalare l'evento. Per TSimpleEvent significa chiamare SetEvent.
+0

Ho curato questione - si trasferisce il codice di attesa a 'AfterConstruction' per rendere classe thread più autosufficiente. Presumo che nulla mi impedisca di usare 'TSimpleEvent' anche in modo contenuto? –

+2

Il luogo perfetto per l'attesa è il metodo 'DoAction' o qualsiasi altro metodo pubblico in cui è necessario assicurarsi che tutto sia in esecuzione –

+0

@Sir Rufo è corretto che è meglio aspettare solo quando è necessario attendere –

2

Come notato da David, un TEvent funzionerebbe per questo, così come qualsiasi numero di altri oggetti di sincronizzazione. A titolo di esempio (scrittura da quando sono stato in gran parte fatto comunque):

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. 
0

Indovinate ho ricevuto la tua 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; 

oppure si può passare dal WaitForSingleObjectAfterConstruction al codice DoAction come proposto Sir 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; 
+1

Basta pensare a se ogni thread impiega 2 secondi per iniziare a correre. La creazione di 10 thread bloccherà per 20 secondi. Guarda la mia risposta, non c'è attesa fino a quando non si chiama l'azione –