2009-06-24 13 views
6

Ho il codice seguente (abbattuto per migliorare la leggibilità):Will .Net Garbage Colleziona un oggetto a cui non si fa riferimento, ma ha un thread che sta funzionando?

principale Classe:

public StartProcess() 
{ 
    Thinker th = new Thinker(); 
    th.DoneThinking += new Thinker.ProcessingFinished(ThinkerFinished); 
    th.StartThinking(); 
} 

void ThinkerFinished() 
{ 
    Console.WriteLine("Thinker finished"); 
} 

Pensatore Classe:

public class Thinker 
{ 
    private System.Timers.Timer t; 

    public delegate void ProcessingFinished(); 
    public event ProcessingFinished DoneThinking; 

    BackgroundWorker backgroundThread; 

    public Thinker() { } 

    public StartThinking() 
    { 
     t = new System.Timers.Timer(5000); // 5 second timer 
     t.AutoReset = false; 
     t.Elapsed += new System.Timers.ElapsedEventHandler(t_Elapsed); 
     t.Start(); 

     // start a background thread to do the thinking 
     backgroundThread = new BackgroundWorker(); 
     backgroundThread.DoWork += new DoWorkEventHandler(BgThread_DoWork); 
     backgroundThread.RunWorkerAsync(); 
    } 

    void t_Elapsed(object sender, System.Timers.ElapsedEventArgs e) 
    { 
     DoneThinking(); 
    } 

    BgThread_DoWork(object sender, DoWorkEventArgs e) 
    { 
     // work in here should go for much less than 5 seconds 
     // it will die if it doesn't 

     t.Stop(); 
     DoneThinking(); 
    } 
} 

Quello che originariamente previsto per accadere era che il gestore di eventi nella classe principale impedirebbe al Thinker di essere raccolto dalla spazzatura.

Apparently this isn't the case.

Ora mi chiedo se la garbage collection si verificherà indipendentemente dal fatto che questo thread sia "occupato" o meno. In altre parole, c'è la possibilità che venga raccolto dei rifiuti prima che sia scaduto il timeout di 5 secondi?

Per dirla in altro modo, è possibile che il garbage collector raccolga il mio pensatore prima che l'elaborazione sia terminata?

risposta

8

No, un thread è considerato dal vivo fintanto che viene fatto riferimento e qualsiasi thread in esecuzione viene considerato come referenziato (IIRC un thread in esecuzione registra il suo stack come una radice GC e tale stack farà riferimento al thread) .

Detto questo, sto guardando il tuo esempio e non capisco dove credi che si stia generando un thread?

+1

Quando si verifica l'evento trascorso sul timer, avviene su una nuova discussione. Forse è di questo che sta parlando. http://msdn.microsoft.com/en-us/library/system.timers.timer.aspx – Joe

+0

Scusate, non ero chiaro: il chunk commentato è davvero più importante di quanto gli abbia dato credito. Cercherò di aggiornare – Damovisa

+0

Inoltre, quello che Joe K ha detto - il Timer stesso che presumevo avrebbe funzionato su un thread diverso. L'evento Elapsed torna sicuramente su un altro thread. – Damovisa

2

Se sto leggendo questo diritto (e potrei essere lontano qui), può essere raccolto perché è non attualmente facendo nulla.

Se nel metodo di avvio erano presenti variabili locali e tale metodo era ancora attivo, tali variabili sarebbero comunque "in ambito" nello stack e fornire una radice per il thread. Ma l'unica variabile che usi è il tuo timer privato, e dal momento che è rootato con il thread stesso e il thread non ha nulla nello stack, non rimane nulla per mantenerlo in vita.

+0

Qualcosa che sto guardando, ma il Timer dovrebbe essere ancora in esecuzione. Non significa che la classe è ancora viva? – Damovisa

+1

Il timer non è in esecuzione. Quando si avvia un timer, crea un oggetto su un thread diverso e dice a Windows di riattivarlo quando il periodo è scaduto. Non è rimasto nulla sul thread corrente. –

4

No, lo stack di un thread in esecuzione funge da root per gli scopi GC. Quella pila vivrà fintanto che il Thread è in esecuzione, quindi il Thread stesso non verrà raccolto per tutto il tempo in cui è in esecuzione.

Ecco uno article che indica (tra le altre cose) quali sono le radici per gli scopi del GC. Per risparmiare tempo, le radici GC sono oggetti globali, oggetti statici, tutti i riferimenti su tutti gli stack di thread e tutti i registri CPU contenenti riferimenti.

0

Sono d'accordo e in disaccordo, Se il riferimento all'oggetto thread viene perso, il thread verrà terminato e i dati verranno raccolti. Nel tuo caso potrebbe non essere tale perché non sta usando thread direttamente e sta usando il timer. Ma se hai chiamato un metodo in un thread e il riferimento del thread è stato perso con la fine del metodo, questo verrà raccolto da GC

3

La tua domanda è un po 'difficile da rispondere. Come Joel, per quanto posso dire, non hai nulla nello stack che faccia riferimento al tuo timer, che è l'unica cosa che fa riferimento al thread. Detto questo, ci si aspetterebbe che l'istanza del Pensatore venisse raccolta.

Ero curioso e avevo bisogno di una spiegazione più concreta di ciò che poteva accadere, così ho scavato un po 'in Reflector. Come risulta, System.Timers.Timer alla fine crea un System.Threading.Timer, che crea internamente un'istanza di TimerBase, una classe interna. TimerBase deriva da CriticalFinalizerObject, che è un tipo di sistema che garantisce che tutto il codice in un'area di esecuzione forzata (CER) verrà eseguito prima che la classe di implementazione sia completamente finalizzata e scartata dal GC. TimerBase è anche IDisposable, e il suo metodo di dispose loop e spinwaits fino a quando non viene rilasciato un lock. A questo punto, ho iniziato a eseguire il codice esterno, quindi non sono esattamente sicuro di come il blocco viene inizializzato o rilasciato.

Tuttavia, in base a come viene scritta la classe TimerBase, il fatto che derivi da CriticalFinalizerObject e il fatto che disponga di spinwait fino al rilascio di un blocco, penso che sia sicuro affermare che un thread a cui non fa riferimento qualsiasi cosa non sarà finalizzata fino a quando il codice non verrà eseguito. Detto questo ... è importante notare che molto probabilmente verrà elaborato dal GC ... molto probabilmente più di una volta, poiché la finalizzazione può allungare notevolmente il processo di raccolta su oggetti finalizzati. Per quelli che sono CriticalFinalizerObjects, il processo di finalizzazione potrebbe richiedere anche più tempo se è in esecuzione attivamente il codice che CER sta assicurando eseguirà completamente.

Ciò potrebbe significare che hai esattamente il problema opposto se i tuoi Pensatori richiedono un po 'di tempo per essere eseguiti. Piuttosto che quegli oggetti raccolti prematuramente, andranno in una lunga fase di finalizzazione, e qualsiasi cosa facciano riferimento finiscono in gen2, e vivranno per un bel po 'di tempo fino a che il GC non sarà finalmente in grado di raccoglierli completamente.

+0

Grazie jrista - alcune cose buone lì dentro. Per riferimento, il thread in background dovrebbe finire quasi immediatamente se sta per finire del tutto. Se capisco correttamente la tua risposta, il timer dovrebbe impedire una garbage collection prematura? – Damovisa

+0

Corretto, il timer dovrebbe impedire la raccolta dei dati inutili. (Questa responsabilità è in realtà delegata a System.Threading.TimerBase, quindi è rimossa di alcuni gradi, ma l'effetto è lo stesso.) – jrista

Problemi correlati