2015-11-23 15 views
5

Questo mi ha effettivamente morso un paio di volte. Se fai un codice semplice come questo:Manca la perdita in. Net threads

private void button1_Click(object sender, EventArgs e) 
    { 

     for (int i = 0; i < 1000000; i++) 
     { 
      Thread CE = new Thread(SendCEcho); 
      CE.Priority = ThreadPriority.Normal; 
      CE.IsBackground = true; 
      CE.Start(); 
      Thread.Sleep(500); 
      GC.Collect(); 
     } 
    } 

    private void SendCEcho() 
    { 
     int Counter = 0; 

     for (int i = 0; i < 5; i++) 
     { 
      Counter++; 
      Thread.Sleep(25); 
     } 
    } 

Esegui questo codice e guarda le maniglie volare! Thread.Sleep è così puoi spegnerlo e non prendere il controllo del tuo computer. Questo dovrebbe garantire che il thread lanciato muoia prima dell'avvio del thread successivo. Chiamando GC.Collect(); non fa nulla. Dalla mia osservazione questo codice perde 10 handle ogni aggiornamento al task manager al normale aggiornamento.

Non importa ciò che è nella funzione SendCEcho() del vuoto, contare fino a cinque se si desidera. Quando il filo muore, c'è una maniglia che non viene pulita.

Con la maggior parte dei programmi questo non ha molta importanza perché non vengono eseguiti per lunghi periodi di tempo. Su alcuni dei programmi che ho creato devono essere eseguiti per mesi e mesi.

Se si esercita questo codice più e più volte, è possibile perdere le maniglie fino al punto in cui il sistema operativo Windows diventa instabile e non funzionerà. Riavvio richiesto.

ho risolto il problema utilizzando un pool di thread e il codice come questo:

ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadJobMoveStudy)); 

La mia domanda però è perché c'è una tale perdita in .Net, e perché è esistito così a lungo? Dal momento che come 1.0? Non sono riuscito a trovare la risposta qui.

+2

Come stai visualizzando le maniglie? –

+0

@Yacoub In Windows Task Manager. Graverà seriamente tutte le maniglie disponibili e renderà instabile il sistema operativo. – Jake

+0

@ pm100 .net mi consente di farlo. Ci sono un sacco di tutorial che mostrano di fare threading come questo. Prometto che se si esegue questo codice più e più volte, non mettendo nulla nella funzione void, si renderà instabile il sistema operativo. – Jake

risposta

3

Prova ad aggiungere GC.WaitForPendingFinalizers(); dopo la chiamata a GC.Collect(); Penso che ti prenderà ciò che cerchi. fonte completa di seguito:

EDIT

anche dare un'occhiata a questo dal 2005: https://bytes.com/topic/net/answers/106014-net-1-1-possibly-1-0-also-threads-leaking-event-handles-bug. Quasi lo stesso codice del tuo.

using System; 
using System.Threading; 

class Program 
{ 
    static void Main(string[] args) 
    { 
     for (int i = 0; i < 1000000; i++) 
     { 
      Thread CE = new Thread(SendCEcho); 
      CE.Priority = ThreadPriority.Normal; 
      CE.IsBackground = true; 
      CE.Start(); 
      Thread.Sleep(500); 
      CE = null; 
      GC.Collect(); 
      GC.WaitForPendingFinalizers(); 
     } 
    } 

    public static void SendCEcho() 
    { 
     int Counter = 0; 
     for (int i = 0; i < 5; i++) 
     { 
      Counter++; 
      Thread.Sleep(25); 
     } 
    } 
} 
+0

OK Ho eseguito nuovamente alcuni test e sembra che tu abbia ragione. Se eseguo questo codice non ho perdite di handle con GC.WaitForPendingFinalizers() aggiunto. Quindi la domanda diventa perché devo chiamare esplicitamente questa funzione o Windows non rilascia le risorse? Pensavo che Garbage Collection potesse accadere automaticamente, almeno alla fine. Apparentemente no, perché senza quella chiamata, uccide il sistema operativo alla fine. – Jake

+2

'Collect()' eseguirà la raccolta, ma gli oggetti finalizzabili verranno inseriti nella coda di finalizzazione. I loro finalizzatori verranno eseguiti dal thread di finalizzazione solo in futuro. Chiamare 'WaitForPendingFinalizers()' fa quello che ci si aspetterebbe: blocca il thread corrente fino a quando il thread del finalizzatore fa la sua cosa. – dlev

+0

@dlev Una risposta così semplice, ma perfetta. Grazie! – Jake

5

Stai creando thread che non finiscono mai. Il ciclo for in SendCEcho non termina mai, quindi i thread non terminano mai e quindi non possono essere recuperati. Se aggiusto il tuo codice loop, i thread terminano e vengono recuperati come previsto. Non riesco a riprodurre il problema con il codice qui sotto.

static void SendCEcho() 
{ 
    int Counter = 0; 

    for (int i = 0; i < 5; i++) 
    { 
     Counter++; 
     Thread.Sleep(25); 
    } 
} 
+0

Come può un ciclo su cinque, finito, non finire mai? for (int i = 0; i <5; i ++) ??? – Jake

+0

Il tuo codice iniziale ha letto 'for (int i = 0; i <5 + i ++)' - che non terminerà mai. Il tuo codice corretto non viene compilato. Il punto qui è che * importa * ciò che fa il metodo thread. –

5
for (int i = 0; i < 5 + i++;) 

Abbastanza bizzarro errore di battitura, non hai ricreato il problema reale. Ce n'è uno, un oggetto Thread consuma 5 handle di sistema operativo, il suo finalizzatore interno li rilascia. Una classe .NET normalmente ha un metodo Dispose() per garantire che tali handle possano essere rilasciati in anticipo ma Thread non ne ha uno. Si trattava di un design coraggioso, un tale metodo Dispose() sarebbe molto difficile da chiamare,.

Quindi dover fare affidamento sul finalizzatore è un requisito irrinunciabile. In un programma che ha un metodo "SendEcho" e non fa altro, si corre il rischio che il garbage collector non venga mai eseguito. Quindi il finalizzatore non può fare il suo lavoro. Spetta a te chiamare GC.Collect(). Considereresti di farlo ogni, diciamo, 1000 discussioni che inizi. Oppure usa ThreadPool.QueueUserWorkItem() o Task.Run() in modo da riciclare i thread, l'approccio logico.

Utilizzare Perfmon.exe per verificare che il GC non sia effettivamente in esecuzione. Aggiungi il contatore delle memorie CLR .NET> # Gen 0 per il tuo programma.

+0

Scusa se ho sbagliato, ma è solo un ciclo for. – Jake

+0

Lo sapevamo già, concentrati solo sul resto del post. –