2012-06-26 10 views
5

Mam aplikację (WPF), która tworzy BitmapImages w wielkich liczbach (np. 25000). Wygląda na to, że framework wykorzystuje pewną wewnętrzną logikę, więc po utworzeniu jest około 300 MB zużytej pamięci (150 wirtualnych i 150 fizycznych). Te BitmapImages są dodawane do obiektu Image i są dodawane do Canvas. Problem polega na tym, że po zwolnieniu wszystkich tych obrazów pamięć nie jest zwalniana. Jak mogę odzyskać pamięć?Czyszczenie pamięci nie pozwala odzyskać BitmapImage?

Aplikacja jest prosta: 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-za

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

To jest zrzut ekranu ResourceManager Po dodaniu zdjęć enter image description here

+1

"około 300 MB zużytej pamięci (150 wirtualnych i 150 fizycznych)", która jest całkowicie BOGUS. Czytaj w pamięci. – leppie

+2

Nie należy używać GC.Collect(), ale Bitmap.Dispose(). –

+0

Twoje wezwanie "GC" nic tam nie zrobi. Zakładając, że nie ma wybitnych odniesień do zrootowanych obiektów 'BitmapImage', CLR odzyska pamięć, kiedy będzie jej potrzebować. – MoonKnight

Odpowiedz

1

To wciąż tablicą

BitmapImage[] bimages = new BitmapImage[size]; 

Tablice są ciągłymi strukturami danych o stałej długości, po przydzieleniu pamięci dla całej tablicy nie można odzyskać jej części. Spróbuj użyć innych struktur danych (takich jak LinkedList<T>) lub innych, bardziej odpowiednich w twoim przypadku.

+3

Tablica będzie rozmiarem * rozmiaru wskaźnika, nie bierze pod uwagę przestrzeni sterty pobranej przez wskazywane obiekty, które zgodnie z powyższym przykładem zostały usunięte i dereferencje. To stwierdzenie, choć dokładne, nie wyjaśni, dlaczego użycie pamięci nie zmienia się po zbiorze śmieci. –

+0

@AdamHouldsworth Tablica jest odniesieniem do zmiennej 'bimages', przechowywanej na lokalnym stosie zmiennych. Po zakończeniu tej metody ta zmienna lokalna wyskakuje poza zakres, co oznacza, że ​​nic nie pozostaje do odniesienia do tablicy na stercie pamięci. Osierocona matryca kwalifikuje się następnie do odzyskania przez GC. Jednak ta kolekcja może nie nastąpić natychmiast, ponieważ decyzja CLR o tym, czy należy je zbierać, opiera się na wielu czynnikach (dostępna pamięć, bieżący przydział pamięci itd.). Oznacza to, że istnieje nieokreślone opóźnienie w odniesieniu do czasu, jaki upłynął przed odśmiecaniem. – MoonKnight

+0

@Killercam 'bimages' nie jest metodą lokalną, jest zmienną składową klasy (tak przypuszczam, że jest używana w obu powyższych metodach). Zgadzam się, że GC jest nieokreślone, ale "GC.Collect" jest zdeterminowane - to tylko dyskusja na temat tego, co powinno być uprawnione. –

5

W WPF pojawił się błąd, że nas ugryziono, gdy obiekty BitmapImage nie zostały zwolnione, chyba że je zablokujesz. http://blogs.msdn.com/b/jgoldb/archive/2008/02/04/finding-memory-leaks-in-wpf-based-applications.aspx była oryginalną stroną, na której znaleźliśmy problem. Powinien on zostać naprawiony w Wpf 3.5 sp1, ale wciąż widzieliśmy go w niektórych sytuacjach. Spróbuj zmienić swój kod tak, aby sprawdzić, czy to jest problem:

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

Rutynowo zamrozić nasze obiekty BitmapImage teraz jak byliśmy zobaczyć inne przypadki, w profilera gdzie WPF została słuchania dla wydarzeń na BitmapImage i tym samym utrzymując obraz żywy.

Jeśli wywołanie Feeze() nie jest oczywistą poprawką dla twojego kodu, zdecydowanie polecam użycie profilera, takiego jak Profiler pamięci RedGate - który prześledzi drzewo zależności, które pokaże ci, co to jest utrzymywanie twoje obiekty obrazu w pamięci.

+5

Czy masz jakieś źródła informacji na temat tego błędu? –

+0

Edytowane powyżej, aby uwzględnić oryginalny adres URL i rekomendację Profiler, jeśli funkcja Freeze() nie jest oczywistą poprawką. – fubaar

+1

Po prostu ciekawa, czy funkcja Freeze() rozwiązała problem w twoim scenariuszu? – fubaar

2

Co pracował dla mnie było:

  1. Ustaw ImageSource formant obrazu za null
  2. Run UpdateLayout() przed wyjęciem sterowania, który zawiera obraz z UI.
  3. Upewnij się, że zatrzymałeś() obraz bitmapowy podczas tworzenia go i że nie było żadnych słabych odniesień do obiektów BitmapImage używanych jako ImageSources.

Moja metoda czyszczenia dla każdego obrazu zakończył się tak proste, jak to:

img.Source = null; 
UpdateLayout(); 

udało mi się osiągnąć to poprzez eksperymentowanie utrzymując listę z WeakReference() obiektu, wskazując na każdym BitmapImage które stworzyłem, a następnie sprawdziłem pole IsAlive na WeakReferences po tym, jak miały zostać oczyszczone, aby potwierdzić, że zostały faktycznie oczyszczone.

Więc moje BitmapImage metoda tworzenia wygląda następująco:

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

Ja tylko mówię moje doświadczenie o odzyskaniu pamięci BitmapImage. Pracuję z .Net Framework 4.5.
Tworzę prostą aplikację WPF i ładuję duży plik obrazu. Próbowałem wyczyścić obraz z pamięci za pomocą następującego kodu:

private void ButtonImageRemove_Click(object sender, RoutedEventArgs e) 
    { 

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

Ale to nie zadziałało. Próbowałem też innych rozwiązań, ale nie dostałem odpowiedzi. Po kilku dniach zmagań dowiedziałem się, że jeśli dwa razy nacisnę przycisk, GC zwolni pamięć. następnie po prostu piszę ten kod, aby zadzwonić do odbiorcy GC kilka sekund po kliknięciu przycisku.

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

    } 

ten kod właśnie testowany w DotNetFr 4.5. może musisz zamrozić obiekt BitmapImage dla niższego .Net Framework.
Edycja
Kod ten nie działa, dopóki układ nie zostanie zaktualizowany. Mam na myśli, że jeśli kontrola rodzicielska zostanie usunięta, GC nie odzyska jej.

2

Poszedłem za odpowiedzią udzieloną przez AAAA. Kod Orignal powodując pamięć wypełniona jest:

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

blok kodu włożona aaaa, w C# add "za pomocą System.Threading;" i VB dodać "Imports 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); 

Powtarzanie pętli tego bloku powoduje teraz stały i niski poziom pamięci. Ten kod działał przy użyciu społeczności Visual Studio 2015.

+0

, działa bardzo dobrze. Dziękuję Ci. –

+0

Czy ta odpowiedź dotyczy WPF lub WinForm? Nie sądzę, że aplikacje WPF zazwyczaj używają klas (GDI) 'Graphics' i' Bitmap'. – jrh

Powiązane problemy