2010-08-24 9 views
9

Ho una discussione principale e una discussione separata nel mio programma. Se il thread separato finisce prima del thread principale, dovrebbe liberarsi automaticamente. Se il thread principale finisce per primo, dovrebbe liberare il thread separato.Gratuito un TThread automaticamente o manualmente

Conosco FreeOnTerminate e ho letto che devi stare attento ad usarlo.

La mia domanda è, il seguente codice è corretto?

procedure TMyThread.Execute; 
begin 
    ... Do some processing 

    Synchronize(ThreadFinished); 

    if Terminated then exit; 

    FreeOnTerminate := true; 
end; 

procedure TMyThread.ThreadFinished; 
begin 
    MainForm.MyThreadReady := true; 
end; 

procedure TMainForm.Create; 
begin 
    MyThreadReady := false; 

    MyThread := TMyThread.Create(false); 
end; 

procedure TMainForm.Close; 
begin 
    if not MyThreadReady then 
    begin 
    MyThread.Terminate; 
    MyThread.WaitFor; 
    MyThread.Free; 
    end; 
end; 

risposta

8

È possibile semplificare questo a:

procedure TMyThread.Execute; 
begin 
    // ... Do some processing 
end; 

procedure TMainForm.Create; 
begin 
    MyThread := TMyThread.Create(false); 
end; 

procedure TMainForm.Close; 
begin 
    if Assigned(MyThread) then 
    MyThread.Terminate; 
    MyThread.Free; 
end; 

Spiegazione:

  • usare sia FreeOnTerminate o libero il filo manualmente, ma mai fare entrambe le cose. La natura asincrona dell'esecuzione del thread significa che si corre il rischio di non liberare il thread o (molto peggio) di farlo due volte. Non vi è alcun rischio nel mantenere l'oggetto thread dopo aver terminato l'esecuzione, e non vi è alcun rischio nel chiamare Terminate() su un thread che ha già finito.

  • Non è necessario sincronizzare l'accesso a un booleano che viene scritto solo da un thread e letto da un altro. Nel peggiore dei casi si ottiene il valore sbagliato, ma a causa dell'esecuzione asincrona è comunque un effetto spurio. La sincronizzazione è necessaria solo per i dati che non possono essere letti o scritti in modo atomico. E se hai bisogno di sincronizzare, non usare Synchronize() per questo.

  • Non è necessario avere una variabile simile a MyThreadReady, poiché è possibile utilizzare WaitForSingleObject() per interrogare lo stato di un thread. Passa al numero MyThread.Handle come primo e a 0 come secondo parametro e controlla se il risultato è WAIT_OBJECT_0 - in caso affermativo il tuo thread ha terminato l'esecuzione.

BTW: non utilizzare l'evento OnClose, utilizzare OnDestroy invece. Il primo non è necessariamente chiamato, nel qual caso il thread potrebbe continuare a essere eseguito e mantenere in vita il processo.

+0

Ciao! FreeOnTerminate da solo non è un'opzione. D'altra parte non voglio che il thread esegua la memoria durante l'esecuzione del programma principale. Sto sincronizzando il booleano perché credo che garantisca che verrà eseguito prima di MainForm.Close o dopo MainForm.Close. Quindi MyThread.Terminate verrà chiamato solo se FreeOnTerminate è falso. Sto sbagliando qui? – Steve

+0

Quanta memoria ha il thread 'hog' una volta finito? Se non puoi dire di non avere motivo di preoccuparti. Misura prima. Ma se insisti, quindi pubblica un messaggio dal tuo thread come ultima cosa e libera il thread nel gestore del messaggio. 'Synchronize()' è troppo vile anche solo per pensare se il tuo codice funzionerebbe in tutte le circostanze. Dì solo di no. – mghie

+0

Accettare questa soluzione come la più pulita. Grazie a tutti per le risposte! – Steve

2

No, il codice non è buono (anche se probabilmente funzionerà in 99,99% o addirittura il 100% dei casi). Se hai intenzione di terminare il thread di lavoro dal thread principale, non impostare FreeOnTerminate su True (non vedo cosa stai cercando di ottenere nel codice precedente impostando FreeOnTerminate su True, almeno rende il tuo codice meno comprensibile) .

Una situazione più importante con la terminazione dei thread di lavoro è che si sta tentando di chiudere un'applicazione mentre il thread di lavoro è in stato di attesa. Il thread non si risveglierà se si chiama Terminate, in genere si dovrebbe utilizzare un ulteriore oggetto syncronization (solitamente un evento) per riattivare il thread di lavoro.

E ancora un'osservazione - non v'è alcuna necessità di

begin 
    MyThread.Terminate; 
    MyThread.WaitFor; 
    MyThread.Free; 
    end; 

se si guarda al codice TThread.Destroy, chiama Terminate and WaitFor, così

MyThread.Free; 

è sufficiente (almeno in Delphi 2009, non ho a disposizione fonti di Delphi 7 per verificare).


Aggiornato

Leggi mghie risposta. Si consideri la seguente situazione (meglio su 1 sistema della CPU):

thread principale sta eseguendo

procedure TMainForm.Close; 
begin 
    if not MyThreadReady then 
    begin 
    MyThread.Terminate; 
    MyThread.WaitFor; 
    MyThread.Free; 
    end; 
end; 

farlo controllare valore MyThreadReady (è False) ed è stato spento dal scheduler.

Ora lo scheduler passa al thread di lavoro; esegue

Synchronize(ThreadFinished); 

e impone lo scheduler per tornare al thread principale. Filo principale continua esecuzione:

MyThread.Terminate; // no problem 
    MyThread.WaitFor;  // ??? 
    MyThread.Free; 

puoi dire cosa succederà a WaitFor? Non posso (richiede una visione più approfondita delle fonti di TThread per rispondere, ma a prima vista sembra una situazione di stallo).

Il tuo errore reale è qualcosa di diverso - hai scritto un codice inaffidabile e stai cercando di scoprire se è corretto o meno. Questa è una cattiva pratica con i thread: dovresti imparare a scrivere un codice affidabile.

Come per le risorse: quando TThread (con FreeOnTerminate = False) viene terminato, le sole risorse che rimangono allocate sono l'handle di thread di Windows (non utilizza risorse di Windows sostanziali dopo che il thread è terminato) e l'oggetto Delphi TThread in memoria. Non è un grosso costo essere dalla parte della sicurezza.

+0

Se non si imposta FreeOnTerminate su true, il thread non verrà liberato fino al termine del thread principale. Minuti o anche ore possono passare fino a quando le risorse del thread separato non vengono liberate - non lo voglio. Non capisco cosa intendi nel secondo paragrafo della tua risposta. In tal caso, il codice sopra non funzionerà? Cos'è lo 00.01%? – Steve

4

La thread principale assegna un gestore all'evento OnTerminate del thread di lavoro. Se il thread di lavoro termina per primo, il gestore può segnalare il thread principale per liberare il thread. Se il thread principale termina per primo, può terminare il thread worker. Per esempio:

procedure TMyThread.Execute; 
begin 
    ... Do some processing ... 
end; 

procedure TMainForm.Create; 
begin 
    MyThread := TMyThread.Create(True); 
    MyThread.OnTerminate := ThreadFinished; 
    MyThread.Resume; // or MyThread.Start; in D2010+ 
end; 

const 
    APPWM_FREE_THREAD = WM_APP+1; 

procedure TMainForm.ThreadFinished(Sender: TObject); 
begin 
    PostMessage(Handle, APPWM_FREE_THREAD, 0, 0); 
end; 

procedure TMainForm.WndProc(var Message: TMessage); 
begin 
    if Message.Msg = APPWM_FREE_THREAD then 
    StopWorkerThread 
    else 
    inherited; 
end; 

procedure TMainForm.StopWorkerThread; 
begin 
    if MyThread <> nil then 
    begin 
    MyThread.Terminate; 
    MyThread.WaitFor; 
    FreeAndNil(MyThread); 
    end; 
end; 

procedure TMainForm.Close; 
begin 
    StopWorkerThread; 
end; 
+0

Non mi piace. Invece di rimuovere il problema, lo stai nascondendo più in profondità, quindi è sempre più difficile scoprire dove il codice potrebbe fallire. – kludg

+2

No, non sto nascondendo nulla. Questo è un modo molto più efficace per risolvere il problema originale e funziona perfettamente. Il thread di lavoro non dovrebbe cercare di liberarsi direttamente da tutti. Poiché il thread principale è quello che avvia il thread e deve comunque gestire il thread, dovrebbe essere quello che libera il thread. TThread ha un evento OnTerminate appositamente allo scopo di notificare altro codice quando il thread termina, e realizza la stessa cosa ESATTA della chiamata originale 'Synchronize (ThreadFinished)' ... ... –

+2

... Il PostMessage () il trucco è solo per ritardare l'effettiva liberazione del thread di pochi millisecondi, dal momento che non può essere eseguito all'interno dell'evento OnTerminate stesso. Ma il thread di lavoro viene ancora auto-liberato quando termina, non viene semplicemente liberato dall'interno del thread stesso, questa è l'unica differenza. –

0

Onestamente, il vostro


... Do some processing 

è il vero problema qui. È un loop per fare qualcosa in modo ricorsivo? Se no e, invece, questo è un compito enorme, dovresti considerare suddividere questa attività in piccole procedure/funzioni e metterle tutte insieme nel corpo di esecuzione, chiamando una dopo l'altra con condizionale per conoscere lo stato del thread, come:



While not Terminated do 
begin 

    if MyThreadReady then 
    DoStepOneToTaskCompletion 
    else 
    clean_and_or_rollback(Something Initialized?); 

    if MyThreadReady then 
    DoStepTwoToTaskCompletion 
    else 
    clean_and_or_rollback(Something Initialized?, StepOne); 

    if MyThreadReady then 
    DoStepThreeToTaskCompletion 
    else 
    clean_and_or_rollback(Something Initialized?, StepOne, StepTwo); 

    Self.DoTerminate; // Not sure what to expect from that one 
end; 

È sporco, quasi un hack, ma funzionerà come previsto.

Chi FreeOnTerminate, beh ... è sufficiente rimuovere la dichiarazione e sempre


FreeAndNil(ThreadObject); 

io non sono un fan di syncronise. Mi piacciono le sezioni più critiche, per la flessibilità di estendere il codice per gestire più dati condivisi.

Sulla sezione pubblica modulo, dichiara:

ControlSection : TRTLCriticalSection; 

sulla forma creare o da qualche altra parte prima di thread.Creare,

InitializeCriticalSection(ControlSection); 

Poi, ogni volta che si scrive a una risorsa condivisa (tra cui la variabile MyThreadReady), fanno


EnterCriticalSection (ControlSection); 
    MyThreadReady := True; //or false, or whatever else 
LeaveCriticalSection (ControlSection); 

Prima di andare (uscita), chiamano


DeleteCriticalSection (ControlSection); 

e libera la tua discussione come fai sempre.

saluti Rafael

0

vorrei affermare che i modelli di miscelazione non è semplicemente consigliato. O usi FreeOnTerminate e non tocchi mai più il filo, altrimenti non lo fai. In caso contrario, è necessario un modo protetto per comunicare con i due.

Poiché si desidera un controllo preciso sulla variabile del thread, non utilizzare FreeOnTerminate. Se il thread termina in anticipo, deselezionare le risorse locali che il thread ha consumato normalmente, quindi lasciare semplicemente che il thread principale liberi il thread secondario al termine dell'applicazione. Otterrai il meglio di entrambi i mondi - risorse liberate dal thread figlio non appena possibile, e non ti preoccupare della sincronizzazione dei thread. (E ha anche il vantaggio di essere molto più semplice nella progettazione/codice/comprensione/supporto ...)

Problemi correlati