È corretto eseguire GC.Collect in un thread in background?

Seguendo questa risposta SO , sto facendo:

ThreadPool.QueueUserWorkItem( delegate { GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); }); 

Il mio objective è quello di eseguire una garbage collection dopo aver chiuso un modulo di WinForms con molte immagini / controlli PictureBox per garantire che non abbia più immagini in memoria. (Credo di seguire le istruzioni di Jon Skeet ).

Lo sto facendo in un thread in background per provare ad avere la mia interfaccia utente retriggers.

La mia domanda:

Mi porta qualche vantaggio nel fare la garbage collection in un thread in background? Oppure rende la mia applicazione più lenta / dura più a lungo?

Stai buttando via l’opzione per fare eseguire la garbage collection sullo sfondo quando fai questo. In altre parole, il thread dell’interfaccia utente verrà comunque sospeso, indipendentemente dal fatto che lo si faccia da un thread di lavoro. L’unico modo ansible per essere avanti è quando GC.WaitForPendingFinalizers () impiega una notevole quantità di tempo. Non è in realtà qualcosa che dovresti mai aspettarti, non ha senso, e se ci vuole più di un batter d’occhio allora stai nascondendo bug piuttosto seri nel tuo codice.

Un’altra ruga significativa è che la versione workstation di Windows fornisce a qualsiasi thread che possiede la finestra in primo piano un quantum più grande. In altre parole, è consentito masterizzare il core più a lungo di un thread in background. Un semplice trucco per rendere Windows più reattivo per l’utente.

Troppe parti in movimento, è davvero meglio mettere alla prova la tua teoria, così puoi essere sicuro che gestire una collezione su un lavoratore è in realtà qualcosa che ti aspetta. Misurare le sospensioni del thread dell’interfaccia utente è piuttosto semplice, è ansible utilizzare un timer per farlo. Il suo evento Tick non può essere eseguito quando il thread è sospeso. Avviare un nuovo progetto Winforms, rilasciare un timer nel modulo, impostare il suo intervallo su 1 e abilitato su True, aggiungere un’etichetta e utilizzare questo codice per misurare i ritardi:

  int prevtick = 0; int maxtick = -1; private void timer1_Tick(object sender, EventArgs e) { int tick = Environment.TickCount; if (prevtick > 0) { int thistick = tick - prevtick; if (thistick > maxtick) { maxtick = thistick; label1.Text = maxtick.ToString(); } } prevtick = tick; } 

Esegui il tuo programma, dovresti vedere 16 nell’etichetta. Se si ottiene meno di quanto si dovrebbe ottenere riparato la macchina, non altrimenti nulla che influisce su questo test. Aggiungi un pulsante per ripristinare la misurazione:

  private void button1_Click(object sender, EventArgs e) { maxtick = -1; } 

Aggiungi una casella di controllo e un altro pulsante. Lo faremo eseguire la raccolta effettiva:

  private void button2_Click(object sender, EventArgs e) { var useworker = checkBox1.Checked; System.Threading.ThreadPool.QueueUserWorkItem((_) => { var lst = new List(); for (int ix = 0; ix < 500 * 1024 * 1024 / (IntPtr.Size * 3); ++ix) { lst.Add(new object()); } lst.Clear(); if (useworker) { GC.Collect(); GC.WaitForPendingFinalizers(); } else { this.BeginInvoke(new Action(() => { GC.Collect(); GC.WaitForPendingFinalizers(); })); } }); } 

Gioca con questo, premi il pulsante 2 per iniziare la raccolta e fai attenzione al valore nell’etichetta. Attivare la casella di controllo in modo che venga eseguita sul worker e confrontata. Utilizzare il pulsante 1 per reimpostare il massimo in mezzo. E modifica il codice di allocazione, probabilmente vorrai fare qualcosa con le bitmap, qualunque cosa tu faccia per richiedere questo hack.

Quello che vedo: ~ Ritardo di 220 msec quando si esegue la raccolta sul thread dell’interfaccia utente, ~ 340 msec di ritardo durante l’esecuzione sul worker. Chiaramente, questo non è affatto un miglioramento. Da dove mi siedo, la tua teoria è morta nell’acqua. Si prega di provare questo te stesso, ho solo un punto di dati. Fai attenzione che apparirà molto diverso su una versione server di Windows o con nel file .config. Qualcos’altro con cui puoi giocare.

Aggiornamento: il ragionamento in questa risposta sembra piuttosto buono, ma la risposta di Hans Passant in basso mostra che la conclusione non regge. Non saltare alle conclusioni sulla base di questa risposta.


Questa è una buona idea. Tutti gli algoritmi CLC GC sospendono ogni thread almeno una volta, ma le pause sono inferiori al tempo totale del GC. La chiamata a GC.Collect impiega tutto il tempo impiegato dal GC totale. Ha la massima latenza ansible per qualsiasi ciclo GC. Ecco perché è una buona idea non chiamarlo sul thread dell’interfaccia utente.

Il thread dell’interfaccia utente verrà messo in pausa durante il GC almeno una volta, ma non per l’intera durata. Dipende dalla versione CLR e dalle impostazioni GC per quanto tempo e quante pause ci saranno.

Riepilogo: riduce il tempo di pausa dell’interfaccia utente, ma non lo evita completamente. Consiglio di farlo poiché non si fa del male.

In alternativa, smaltisci tutte le risorse non gestite. Questa domanda sembra assumere una situazione in cui ciò non è ansible o troppo oneroso.

Chiamare direttamente il GC è generalmente una brutta cosa. La class Forms implementa il pattern Dispose quindi perché non lo usi.

Non c’è niente di sbagliato nel chiamare GC.Collect nel thread BackGround. In realtà non fa alcuna differenza. È solo un filo; questo è tutto.

Ma non sono sicuro del perché chiami GC.Collect due volte. AFAIK GC.Collect seguito da GC.WaitForPendingFinalizers è sufficiente.

Forzando GC in thread in background puoi rendere l’interfaccia utente retriggers, ma consumerà le stesse risorse della CPU come se utilizzassi il thread dell’interfaccia utente. Se mantenere l’interfaccia utente retriggers è l’objective, sì è ansible.

Detto questo, come regola generale non chiami GC.Collect nel tuo codice di produzione. Perché lo faresti? Se un modulo di grandi dimensioni viene chiuso e tutti gli oggetti al suo interno sono idonei per la raccolta, Next GC lo raccoglierà. Che beneficio ottieni raccogliendolo immediatamente?

Anche forzando la raccolta dei rifiuti tramite GC.Collect l’euristica interna di GC. Regolerà il limite di soglia dei segmenti per la raccolta ottimizzata per l’attività di allocazione della memoria dell’applicazione, chiamando GC.Collect che stai rovinando tutto.