2012-06-26 10 views
5

Ich habe eine Anwendung (WPF), die BitmapImages in großen Zahlen (wie 25000) erstellt. Scheint wie Framework verwendet einige interne Logik, so nach der Erstellung sind ca. 300 MB Speicher verbraucht (150 virtuelle und 150 physikalische). Diese BitmapImages werden dem Image-Objekt hinzugefügt und in Canvas hinzugefügt. Das Problem ist, dass wenn ich all diese Bilder freigebe, der Speicher nicht freigegeben wird. Wie kann ich den Speicher freigeben?Garbage Collection kann BitmapImage nicht zurückgewinnen?

Die Anwendung ist einfach: 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> 

-Code-behind

 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(); 
     } 

Dies ist ein Screenshot von Resourcemanager nach dem Hinzufügen Bilder enter image description here

+1

"ca. 300 MB Speicher verbraucht (150 virtuelle und 150 physikalische)" das ist total BOGUS. Lesen Sie im Speicher nach. – leppie

+2

Verwenden Sie nicht GC.Collect(), sondern Bitmap.Dispose(). –

+0

Ihr Anruf der 'GC' wird dort nichts tun. Angenommen, es gibt keine ausstehenden Verwurzelungsreferenzen für die "BitmapImage" -Objekte, wird die CLR den Speicher zurückfordern, wenn sie benötigt wird. – MoonKnight

Antwort

1

Dies ist immer noch ein Array

BitmapImage[] bimages = new BitmapImage[size]; 

Arrays sind fortlaufende Datenstrukturen mit fester Länge, sobald der Speicher für das gesamte Array reserviert ist, können Sie keine Teile davon zurückfordern. Versuchen Sie es mit anderen Datenstrukturen (wie LinkedList<T>) oder anderen geeigneten in Ihrem Fall

+3

Das Array wird Größe * Zeigergröße, dies berücksichtigt nicht den Heap-Raum von Objekten, auf die gezeigt wird, die nach dem obigen Beispiel alle entfernt worden sind und dereferenziert. Diese Aussage wird, obwohl sie korrekt ist, nicht erklären, warum sich die Speichernutzung nach einer Speicherbereinigung nicht ändert. –

+0

@AdamHouldsworth Das Array ist ein Verweis auf die Variable "Bimages", die auf dem lokalen Variablenstapel gespeichert ist. Wenn die Methode beendet wird, springt diese lokale Variable aus dem Gültigkeitsbereich heraus, was bedeutet, dass nichts mehr übrig ist, um auf das Array im Speicher-Heap zu verweisen. Das verwaiste Array kann dann vom GC zurückgewonnen werden. Diese Sammlung kann jedoch nicht sofort erfolgen, da die Entscheidung der CLR, ob sie gesammelt werden soll, auf einer Reihe von Faktoren beruht (verfügbarer Speicher, aktuelle Speicherzuweisung usw.). Dies bedeutet, dass es eine unbestimmte Verzögerung bei der Zeit vor der Speicherbereinigung gibt. – MoonKnight

+0

@Killercam 'bimages' ist keine Methode local, es ist eine Klassenmitgliedsvariable (oder so nehme ich an, wie es in beiden Methoden oben verwendet wird). Ich stimme zu, dass GC unbestimmt ist, aber ein "GC.Collect" ist bestimmt - dann ist es nur eine Diskussion darüber, was in Frage kommen sollte. –

5

Es gab einen Bug in Wpf, dass wir gebissen wurden, wo BitmapImage Objekte nicht freigegeben werden, es sei denn Sie sie einfrieren. http://blogs.msdn.com/b/jgoldb/archive/2008/02/04/finding-memory-leaks-in-wpf-based-applications.aspx war die ursprüngliche Seite, auf der wir das Problem entdeckt haben. Es sollte in Wpf 3.5 sp1 behoben worden sein, aber wir sahen es immer noch in einigen Situationen. Versuchen Sie, Ihren Code wie folgt zu ändern, um zu sehen, ob das das Problem ist:

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

Wir haben unsere Bitmap Objekte routinemäßig jetzt einfrieren, wie wir andere Instanzen im Profiler zu sehen waren, wo Wpf wurde auf der Bitmap für Veranstaltungen zu hören und dadurch halten die lebendiges Bild.

Wenn der Aufruf von Feeze() kein offensichtlicher Fix für Ihren Code ist, würde ich Ihnen wärmstens empfehlen, einen Profiler wie den RedGate Memory Profiler zu verwenden, der einen Abhängigkeitsbaum zeigt, der Ihnen zeigt, was er hält Ihre Bildobjekte im Speicher.

+5

Haben Sie Informationen zu diesem Fehler? –

+0

Oben bearbeitet, um die ursprüngliche URL und die Profiler-Empfehlung aufzunehmen, wenn Freeze() kein offensichtlicher Fix ist. – fubaar

+1

Nur neugierig, ob der Freeze() Aufruf das Problem in Ihrem Szenario behoben hat? – fubaar

2

Was für mich gearbeitet wurde an:

  1. die Bildsteuerung des Image Set
  2. Run UpdateLayout() auf null, bevor die Steuerung zu entfernen, die das Bild von der Benutzeroberfläche enthält.
  3. Stellen Sie sicher, dass Sie BitmapImage einfrieren(), wenn Sie es erstellen, und dass die BitmapImage-Objekte, die als ImageSources verwendet werden, keine nicht schwachen Referenzen haben.

Meine Bereinigungsverfahren für jedes Bild endete so einfach wie das ist bis:

img.Source = null; 
UpdateLayout(); 

Ich war eine Liste, indem sie mit einem WeakReference() Objekt zeigt auf jedem Bitmap auf diese durch Experimente gelangen können dass ich das IsAlive-Feld in den WeakReferences erstellt und dann überprüft habe, nachdem sie bereinigt werden sollten, um zu bestätigen, dass sie tatsächlich bereinigt wurden.

Also, meine Bitmaperstellungsmethode sieht wie folgt aus:

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

ich gerade meine Erfahrung zu erzählen über das Gedächtnis Bitmaprückgewinnung. Ich arbeite mit .Net Framework 4.5.
Ich erstelle einfache WPF-Anwendung und lade eine große Bilddatei. Ich habe versucht, das Bild aus dem Speicher mit folgendem Code zu löschen:

Aber es hat nicht funktioniert. Ich habe auch andere Lösungen ausprobiert, aber ich habe meine Antwort nicht bekommen. Nach ein paar Tagen kämpfen, fand ich heraus, wenn ich den Knopf zweimal drücke, wird GC den Speicher freigeben. Dann schreibe ich einfach diesen Code, um den GC-Kollektor einige Sekunden nach dem Klicken auf den Button aufzurufen.

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(); 

    } 

Dieser Code wurde gerade in DotNetFr 4.5 getestet. Vielleicht musst du das BitmapImage Objekt für das untere .Net Framework einfrieren.
Bearbeiten
Dieser Code funktioniert nur, wenn das Layout aktualisiert wird. Ich meine, wenn die Elternkontrolle entfernt wird, kann GC sie nicht zurückfordern.

2

Ich folgte der Antwort von AAAA gegeben. Orignal Code verursacht Speicher gefüllt ist:

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

Inserted AAAA der Codeblock, C# add "mit System.Threading;" und VB hinzufügen "Import 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); 

Wiederholen Looping diesen Block macht jetzt einen stetigen und geringen Speicherbedarf. Dieser Code funktionierte mit Visual Studio 2015 Community.

+0

aufzurufen, das funktioniert sehr gut. Danke. –

+0

Ist diese Antwort für WPF oder Winforms? Ich glaube nicht, dass WPF-Anwendungen typischerweise die (GDI-) Graphics- und Bitmap-Klassen verwenden. – jrh