2012-06-26 12 views
5

Ho un'applicazione (WPF) che crea BitmapImages in numeri enormi (come 25000). Sembra che il framework usi una logica interna, quindi dopo la creazione ci sono circa 300 mb di memoria consumata (150 virtuali e 150 fisici). Questi BitmapImages vengono aggiunti nell'oggetto Immagine e vengono aggiunti a Canvas. Il problema è che quando rilascio tutte quelle immagini la memoria non viene liberata. Come posso liberare la memoria?Garbage Collection non riesce a reclamare BitmapImage?

L'applicazione è semplice: Xaml

<Grid> 
     <Grid.RowDefinitions> 
      <RowDefinition Height="*"/> 
      <RowDefinition Height="Auto"/> 
     </Grid.RowDefinitions> 
     <Grid.ColumnDefinitions> 
      <ColumnDefinition/> 
      <ColumnDefinition/> 
     </Grid.ColumnDefinitions> 
     <Canvas x:Name="canvas" Grid.ColumnSpan="2"></Canvas> 
     <Button Content="Add" Grid.Row="1" Click="Button_Click"/> 
     <Button Content="Remove" Grid.Row="1" Grid.Column="1" Click="Remove_click"/> 
    </Grid> 

codice sottostante

 const int size = 25000; 
     BitmapImage[] bimages = new BitmapImage[size]; 
     private void Button_Click(object sender, RoutedEventArgs e) 
     { 
      var paths = Directory.GetFiles(@"C:\Images", "*.jpg"); 
      for (int i = 0; i < size; i++) 
      { 
       bimages[i] = new BitmapImage(new Uri(paths[i % paths.Length])); 
       var image = new Image(); 
       image.Source = bimages[i]; 
       canvas.Children.Add(image); 
       Canvas.SetLeft(image, i*10); 
       Canvas.SetTop(image, i * 10); 
      } 
     } 

     private void Remove_click(object sender, RoutedEventArgs e) 
     { 
      for (int i = 0; i < size; i++) 
      { 
       bimages[i] = null; 
      } 
      canvas.Children.Clear(); 
      bimages = null; 
      GC.Collect(); 
      GC.Collect(); 
      GC.Collect(); 
     } 

Questa è una schermata di ResourceManager dopo aggiungere immagini enter image description here

+1

"circa 300 MB di memoria consumata (150 virtuali e 150 fisici)" che è totalmente BOGUS. Leggi sulla memoria. – leppie

+2

Non utilizzare GC.Collect(), ma Bitmap.Dispose(). –

+0

La tua chiamata 'GC' non farà nulla lì. Supponendo che non ci siano riferimenti radicati in sospeso per gli oggetti 'BitmapImage', il CLR recupererà la memoria quando ne ha bisogno. – MoonKnight

risposta

1

Questo è ancora un array

BitmapImage[] bimages = new BitmapImage[size]; 

Le matrici sono strutture dati continue a lunghezza fissa, una volta allocata memoria per l'intero array, non è possibile recuperarne parti. Prova ad usare altre strutture di dati (come LinkedList<T>) o altre più appropriate nel tuo caso

+3

La matrice sarà dimensione * dimensione puntatore, questo non tiene conto dello spazio heap preso dagli oggetti puntati, che secondo l'esempio sopra, sono stati tutti rimossi e dereferenziati. Quindi questa affermazione, anche se accurata, non spiegherà perché l'utilizzo della memoria non cambia dopo una garbage collection. –

+0

@AdamHouldsworth L'array fa riferimento alla variabile 'bimages', memorizzata nello stack delle variabili locali. Quando il metodo termina, questa variabile locale viene esclusa dall'ambito, il che significa che non viene lasciato nulla per fare riferimento all'array sull'heap della memoria. L'array orfano diventa quindi idoneo per essere recuperato dal GC. Tuttavia, questa raccolta potrebbe non avvenire immediatamente, in quanto la decisione CLR in merito alla possibilità di raccolta dipende da una serie di fattori (memoria disponibile, allocazione attuale della memoria, ecc.). Ciò significa che c'è un ritardo indeterminato sul tempo impiegato prima della garbage collection. – MoonKnight

+0

@Killercam 'bimages' non è un metodo locale, è una variabile membro della classe (o così presumo come è usato in entrambi i metodi sopra). Sono d'accordo che GC è indeterminato, ma un 'GC.Collect' è determinato - quindi è solo una discussione su cosa dovrebbe essere ammissibile. –

5

C'era un bug in Wpf che veniva morso da dove gli oggetti BitmapImage non vengono rilasciati a meno che non li blocchi. http://blogs.msdn.com/b/jgoldb/archive/2008/02/04/finding-memory-leaks-in-wpf-based-applications.aspx era la pagina originale in cui abbiamo rilevato il problema. Dovrebbe essere stato corretto in Wpf 3.5 sp1 ma lo stavamo ancora vedendo in alcune situazioni. Provare a cambiare il codice come questo per vedere se questo è il problema:

bimages[i] = new BitmapImage(new Uri(paths[i % paths.Length])); 
bimages[i].Freeze(); 

Noi abitualmente congelare i nostri oggetti BitmapImage ora come abbiamo visto altri esempi nel profiler in cui WPF è stato in ascolto per gli eventi sulla BitmapImage e mantenendo in tal modo la immagine viva.

Se la chiamata Feeze() non è una soluzione ovvia per il codice, si consiglia vivamente di utilizzare un profiler come il RedGate Memory Profiler - che traccia un albero di dipendenza che ti mostrerà cosa sta mantenendo i tuoi oggetti di immagine in memoria.

+5

Hai qualche fonte di informazione su questo bug? –

+0

Modificato sopra per includere l'url originale e la raccomandazione di Profiler se Freeze() non è una soluzione ovvia. – fubaar

+1

Sei curioso di sapere se la chiamata a Freeze() ha risolto il problema nel tuo scenario? – fubaar

2

Quello che ha funzionato per me è stato quello di:

  1. Impostare ImageSource del controllo Image su null
  2. Run UpdateLayout() prima di rimuovere il controllo che contiene l'immagine dall'interfaccia utente.
  3. Assicurati di bloccare() BitmapImage quando lo crei e che non sono presenti riferimenti non deboli agli oggetti BitmapImage utilizzati come ImageSources.

Il mio metodo di pulizia per ogni immagine ha finito per essere semplice come questo:

img.Source = null; 
UpdateLayout(); 

ero in grado di arrivare a questo attraverso la sperimentazione, mantenendo una lista con un oggetto WeakReference() indicando ogni BitmapImage che ho creato e quindi controllato il campo IsAlive sul WeakReferences dopo che dovevano essere ripuliti per confermare che erano stati effettivamente ripuliti.

Quindi, il mio metodo di creazione BitmapImage assomiglia a questo:

var bi = new BitmapImage(); 
using (var fs = new FileStream(pic, FileMode.Open)) 
{ 
    bi.BeginInit(); 
    bi.CacheOption = BitmapCacheOption.OnLoad; 
    bi.StreamSource = fs; 
    bi.EndInit(); 
} 
bi.Freeze(); 
weakreflist.Add(new WeakReference(bi)); 
return bi; 
1

Sto solo dicendo la mia esperienza circa recupero della memoria BitmapImage. Lavoro con .Net Framework 4.5.
Creo un'applicazione WPF semplice e carica un file di immagine di grandi dimensioni. Ho provato a cancellare l'immagine dalla memoria usando il seguente codice:

private void ButtonImageRemove_Click(object sender, RoutedEventArgs e) 
    { 

     image1.Source = null; 
     GC.Collect(); 
    } 

Ma non ha funzionato. Ho provato anche altre soluzioni, ma non ho avuto la mia risposta. Dopo alcuni giorni di difficoltà, ho scoperto che se premevo il pulsante due volte, GC libera la memoria. poi scrivo semplicemente questo codice per chiamare GC collector qualche secondo dopo aver fatto clic sul pulsante.

private void ButtonImageRemove_Click(object sender, RoutedEventArgs e) 
    { 

     image1.Source = null; 
     System.Threading.Thread thread = new System.Threading.Thread(new System.Threading.ThreadStart(delegate 
     { 
      System.Threading.Thread.Sleep(500); 
      GC.Collect(); 
     })); 
     thread.Start(); 

    } 

questo codice appena testato in DotNetFr 4.5. forse devi bloccare l'oggetto BitmapImage per .Net Framework inferiore.
Modifica
Questo codice non funziona se il layout non viene aggiornato. Voglio dire se il controllo genitore viene rimosso, GC non riesce a recuperarlo.

2

Ho seguito la risposta data da AAAA. Codice Orignal causando la memoria è riempito:

if (overlay != null) overlay.Dispose(); 
overlay = new Bitmap(backDrop); 
Graphics g = Graphics.FromImage(overlay); 

blocco di codice di Inserito AAAA, C# add "usando System.Threading;" e VB aggiungere "Importazioni System.Threading":

if (overlay != null) overlay.Dispose(); 
//--------------------------------------------- code given by AAAA 
Thread t = new Thread(new ThreadStart(delegate 
{ 
    Thread.Sleep(500); 
    GC.Collect(); 
})); 
t.Start(); 
//-------------------------------------------- \code given by AAAA 
overlay = new Bitmap(backDrop); 
Graphics g = Graphics.FromImage(overlay); 

Ripetere looping questo blocco rende ora un ingombro di memoria costante e bassa. Questo codice ha funzionato con Visual Studio 2015 Community.

+0

che funziona molto bene. grazie. –

+0

Questa risposta è per WPF o Winforms? Non penso che le applicazioni WPF in genere utilizzino le classi 'Graphics' e' Bitmap' (GDI). – jrh