2012-01-10 5 views
8

In un dato esempio sto ricevendo un'eccezione quando si chiama AThread.Free.Discussione: L'handle non è valido (6) quando si cerca di liberare un filo sospeso

program Project44; 

{$APPTYPE CONSOLE} 

uses 
    SysUtils, Classes, Windows; 

type 
    TMyException = class(Exception); 

var 
    AThread: TThread; 
begin 
    AThread := TThread.Create(True); 
    try 
    AThread.FreeOnTerminate := True; 
    //I want to do some things here before starting the thread 
    //During the setup phase some exception might occur, this exception is for simulating purpouses 
     raise TMyException.Create('exception'); 
    except 
    AThread.Free; //Another exception here 
    end; 
end. 

Ho due domande:

  1. Come devo liberare l'istanza AThread di TThread in un dato esempio?

  2. Non capisco, perché TThread.Destroy sta chiamando Resume prima di distruggere se stesso. Qual è il punto di questo?

+0

In primo luogo, stai ricevendo errori/avvisi per questo? TThread.Execute è astratto in D2009. IME, dovresti ricevere un avvertimento sulla costruzione di istanze con metodi astratti. Normalmente, TThread.Execute viene sovrascritto in una classe discendente TThread ed è il discendente che viene istanziato. Non ho mai provato a creare direttamente un'istanza di TThread - Sono abbastanza sicuro che alcune eccezioni verrebbero sollevate sul thread di costruzione, sul thread costruito o su entrambi. –

+4

Si può aspettare di impostare 'FreeOnTerminate' fino a poco prima di chiamare' Resume'. –

+0

Immagino che distruggere le chiamate riprendere perché se un thread è stato sospeso non può essere distrutto correttamente in tale stato. –

risposta

15

Non è possibile impostare FreeOnTerminate-Truee chiamata Free sull'istanza thread. Devi fare l'uno o l'altro, ma non entrambi. Così com'è, il tuo codice distrugge la discussione due volte. Non devi mai distruggere un oggetto due volte e, naturalmente, quando il distruttore viene eseguito per la seconda volta, si verificano degli errori.

Quello che succede qui è che da quando hai creato il thread sospeso, non succede nulla fino a quando non liberi esplicitamente il thread. Quando lo fai, il distruttore riprende il thread, aspetta che si completi. Ciò quindi restituisce Free che si chiama nuovamente perché imposta FreeOnTerminate a True. Questa seconda chiamata a Free chiude l'handle. Quindi si ritorna al proc di thread e che chiama ExitThread. Ciò non riesce perché l'handle del thread è stato chiuso.

Come Martin sottolinea nel commento non è necessario creare TThread direttamente dal momento che il metodo TThread.Execute è astratto. Inoltre, non dovresti usare Resume che è deprecato. Utilizzare Start per iniziare l'esecuzione di un thread sospeso.

Personalmente non mi piace usare FreeOnTerminate. L'utilizzo di questa funzione comporta la distruzione del thread su un thread diverso da quello in cui è stato creato. Generalmente lo usi quando vuoi dimenticare il riferimento all'istanza. Questo ti lascia incerto sul fatto che il thread sia stato distrutto o meno al termine del tuo processo, o anche se si stia chiudendo e liberandosi durante la chiusura del processo.

Se è necessario utilizzare FreeOnTerminate, è necessario assicurarsi di non chiamare Free dopo aver impostato FreeOnTerminate su True. Quindi la soluzione più ovvia è impostare FreeOnTerminate su True immediatamente dopo aver chiamato Start e poi dimenticare l'istanza di thread. Se hai qualche eccezione prima che tu sia pronto per iniziare, puoi tranquillamente liberare il thread, dal momento che lo FreeOnTerminate sarebbe ancora False a quel punto.

Thread := TMyThread.Create(True); 
Try 
    //initialise thread object 
Except 
    Thread.Free; 
    raise; 
End; 
Thread.FreeOnTerminate := True; 
Thread.Start; 
Thread := nil; 

Un approccio più elegante sarebbe quella di spostare tutti l'inizializzazione nel costruttore TMyThread. Quindi il codice sarebbe simile a questo:

Thread := TMyThread.Create(True); 
Thread.FreeOnTerminate := True; 
Thread.Start; 
Thread := nil; 
+0

Ci ho pensato.Probabilmente hai ragione, ma il controllo dei thread di Delphi, specialmente con la terminazione, è stato, (e probabilmente lo è ancora), un tale caos che non ho osato postarlo. Ho guardato per un attimo TThread in "classi" e ho deciso di non approfondire l'argomento nel caso avessi trovato qualcosa. –

+0

@MartinJames Questo è esattamente ciò che accade. Il distruttore viene eseguito due volte. Non finisce mai bene. –

+0

@David Se non richiamo AThread.Free in questo specifico esempio, ricevo una perdita di memoria. Quindi, come ho potuto liberare la discussione due volte? – Wodzu

5

La situazione è molto complicata nel tuo caso.

In primo luogo, in realtà non si libera un filo sospeso; un thread è ripreso nel distruttore:

begin 
    Terminate; 
    if FCreateSuspended then 
     Resume; 
    WaitFor; 
    end; 

Dal Terminate viene chiamato prima Resume, il metodo Execute non si esaurisce mai, e filo termina immediatamente dopo essere stato ripreso:

try 
    if not Thread.Terminated then 
    try 
     Thread.Execute; 
    except 
     Thread.FFatalException := AcquireExceptionObject; 
    end; 
    finally 
    Result := Thread.FReturnValue; 
    FreeThread := Thread.FFreeOnTerminate; 
    Thread.DoTerminate; 
    Thread.FFinished := True; 
    SignalSyncEvent; 
    if FreeThread then Thread.Free; 

Ora guardate l'ultima riga - si call destructor (Thread.Free) dal distruttore stesso! Insetto fantastico!


per rispondere alle vostre domande:

  1. Non ci si può utilizzare FreeOnTerminate:= True nel codice;
  2. Si dovrebbe chiedere a Embarcadero perché TThread è progettato in modo tale; la mia ipotesi: alcuni codice (metodo DoTerminate) dovrebbe essere eseguito nel contesto del thread mentre il thread termina.

È possibile inviare una richiesta di funzionalità di controllo di qualità: aggiungi FFreeOnTerminate:= False-TThread.Destroy realizzazione:

destructor TThread.Destroy; 
begin 
    FFreeOnTerminate:= False; 
// everything else is the same 
    .. 
end; 

che dovrebbero impedire chiamata desctructor ricorsiva e rendere il vostro codice valido.

+0

Grazie Serg, riguardo al punto 1: credo di poterlo fare, se lo sposto appena prima del Resume() o anche immediatamente dopo, funzionerà. Ma grazie comunque per la risposta, ora capisco meglio il problema. +1 – Wodzu

+0

Dopo aver chiamato 'Start' (si prega di chiamare' Start' piuttosto che 'Resume') non è giusto. Il thread potrebbe essere completato e quindi sarà troppo tardi per impostare 'FreeOnTerminate'. Quindi si perderebbero il thread e l'handle del sistema operativo. –

+1

+1, @David, non c'è ancora 'TThread.Start' in D2009 (dato che Q è taggato);) Era a partire dalla D2010, quindi' TThread.Resume' qui è corretto. – TLama

Problemi correlati