2013-02-05 9 views
19

Korzystam z poniższego kodu, aby utworzyć kafelek na żywo, oparty na elemencie interfejsu użytkownika. Powoduje, że uiElement na WriteableBitmap, zapisuje bitmap + zwraca nazwę pliku. Ta metoda jest uruchamiana w agencie zadań działających w tle Windows i uruchamiana jest limit pamięci.WriteableBitmap Memory Leak?

private string CreateLiveTileImage(Canvas uiElement, int width, int heigth) 
{ 
    var wbmp = new WriteableBitmap(width, heigth); 
    try 
    { 
     wbmp.Render(uiElement, null); 
     wbmp.Invalidate(); 

     var tileImageName = _liveTileStoreLocation; 
     using (var stream = new IsolatedStorageFileStream(tileImageName, FileMode.Create, FileAccess.Write, IsolatedStorageFile.GetUserStoreForApplication())) 
     { 
     wbmp.SaveJpeg(stream, width, heigth, 0, 100); 
     stream.Close(); 
     } 

     uiElement = null; 
     wbmp = null; 
     GC.Collect(); 
     return "isostore:" + tileImageName; 
    } 
    catch (Exception exception) 
    { 
     // ... 
    } 
    return null; 
} 

Zrobiłem kilka testów i problem jest: Ten metody przecieki pamięci, ale nie wiem dlaczego /gdzie ?!

Zrobiłem też kilka przebiegów testowych - przed pierwszym napotkasz tej metody:

Microsoft.Phone.Info.DeviceStatus.ApplicationCurrentMemoryUsage 
7.249.920 Bytes 

To jest ok, ponieważ debugger jest dołączony, który wykorzystuje około 2 MB pamięci.

robi jakieś kolejne serie tej metody (cofnięty ponownie uruchomić metodę debugger):

Microsoft.Phone.Info.DeviceStatus.ApplicationCurrentMemoryUsage 
8851456 long + 40960 
Microsoft.Phone.Info.DeviceStatus.ApplicationCurrentMemoryUsage 
8892416 long + 245760 
Microsoft.Phone.Info.DeviceStatus.ApplicationCurrentMemoryUsage 
9138176 long + 143360 
Microsoft.Phone.Info.DeviceStatus.ApplicationCurrentMemoryUsage 
9281536 long + 151552 
Microsoft.Phone.Info.DeviceStatus.ApplicationCurrentMemoryUsage 
9433088 long + 143360 
Microsoft.Phone.Info.DeviceStatus.ApplicationCurrentMemoryUsage 
9576448 long + 139264 
Microsoft.Phone.Info.DeviceStatus.ApplicationCurrentMemoryUsage 
9715712 long + 139264 
Microsoft.Phone.Info.DeviceStatus.ApplicationCurrentMemoryUsage 
9859072 long + 143360 
Microsoft.Phone.Info.DeviceStatus.ApplicationCurrentMemoryUsage 
10006528 long + 147456 

więc pamięć, wykorzystywane przez Metoda ta wzrasta.

Ale dlaczego? Moim zdaniem nie ma żadnych odniesień, które uniemożliwiałyby zbieranie przedmiotów.

UPDATE na 04.05.2013

Cześć,

dziękuję za wszystkie odpowiedzi! Zgodnie z sugestią, zmniejszyłem kod + ostatecznie udało mi się odtworzyć problem w kilku liniach kodu.

void Main() 
{ 
    for (int i = 0; i < 100; i++) 
    { 
     CreateImage(); 
    } 
} 

private void CreateImage() 
{ 
    var rectangle = CreateRectangle(); 
    var writeableBitmap = new WriteableBitmap(rectangle, rectangle.RenderTransform); 

    rectangle = null; 
    writeableBitmap = null; 
    GC.Collect(); 
} 

private Rectangle CreateRectangle() 
{ 
    var solidColorBrush = new SolidColorBrush(Colors.Blue); 
    var rectangle = new Rectangle 
    { 
    Width = 1000, 
    Height = 1000, 
    Fill = solidColorBrush // !!! THIS causes that the image writeableBitmap never gets garbage collected 
    }; 

    return rectangle; 
} 

Po uruchomieniu aplikacji: ApplicationCurrentMemoryUsage: "11 681 792 bajty"

1 iteracji - ApplicationCurrentMemoryUsage: "28 090 368 bajtów"

5 iteracji - ApplicationCurrentMemoryUsage: "77 111 296 bajtów"

20 powtórzeń - ApplicationCurrentMemoryUsage: "260 378 624 bajtów"

Po 23 iteracjach: Wyjątek pamięci. Ln .: var writeableBitmap = new WriteableBitmap (rectangle, rectangle.RenderTransform);

Tylko komentując linię "Fill = solidColorBrush", metoda CreateImage() została wywołana 100 razy bez żadnych problemów - po 100. iteracji zużycie pamięci wynosiło "16 064 512 Bytes".

Wydaje się, że problemem jest pędzel !! Podczas wypełniania elementu interfejsu użytkownika, a później ten element interfejsu użytkownika jest renderowany na zapisywalnej mapie bitowej, bitmapa nigdy nie zbiera śmieci.

Oczywiście to nie ma sensu w mojej opinii. Pędzel rozchodzi się poza zasięgiem, więc również powinien być zbierany śmieci! (Ustawianie szczotki na null po tym jak został użyty nic nie zmieni)

Wielu moich elementów interfejsu użyć szczotki do napełniania, więc nie można po prostu usunąć zużycia szczotek. Co sądzisz o tym problemie?

+1

Należy zauważyć, że zazwyczaj nie ma powodu, aby wywoływać bezpośrednio GC.Collect(). Może to być szkodliwe, zamrozić aplikację i uzyskać wydajność. –

+2

To prawda, to było coś w rodzaju "desperackiej próby", w rzeczywistości nic nie robi, a także nie ma wpływu na zużycie pamięci. – Hannes

+0

Ciekawi Cię, co stanie się, jeśli sfinalizujesz płótno? – Tebc

Odpowiedz

10

Problem polega na tym, że rectangle.RenderTransform jest instancją obiektu i jeśli ustawisz wartość writableBitmap na wartość null, obiekt rectangle.RenderTransform będzie nadal żywy i będzie zawierał prostokąt w pamięci ... dlatego rozwiązaniem jest edycja kodu w następujący sposób:

private void CreateImage() 
{ 
    var rectangle = CreateRectangle(); 
    var writeableBitmap = new WriteableBitmap(rectangle, rectangle.RenderTransform); 

    rectangle.RenderTransform = null; //and your memory would be happy ;) 
    rectangle = null; 
    writeableBitmap = null; 
    GC.Collect(); 
} 

zobaczyć screeny z pamięcią ...

przed:

memory without setting rectangle.RenderTransform to null

po:

memory with setting rectangle.RenderTransform to null

+0

Jak uzyskać te analizy pamięci? Używam podglądu VS 2013/Ultimate. –

+0

Możesz otworzyć tę perspektywę poprzez: DEBUG> Rozpocznij analizę wydajności (Alt + F2) –

+0

Dziękuję, znalazłem to. –

0

W końcu w haczyku próbnym powinno być wbmp = zero, aby rozpocząć grę.

I renderujesz to, co oznacza, że ​​dołączasz ten obiekt do listy zewnętrznej (funkcji). Dlatego żadna ilość GC.Collect nie odbierze go, ponieważ nadal jest "w grze".

Ustaw źródło zasilania na wartość null, aby pozbyć się dołączonego odniesienia, dlaczego GC je ignoruje.

+0

dziękuję, próbowałem obu, ale to nie pomogło - zobacz zaktualizowany post - problem nadal istnieje. – Hannes

0

Można ograniczyć rozmiar zapisywalny bitmapy obrazu szerokość x wysokość do mniejszych rozmiarów, jak kiedy zwiększyć jej rozmiar, jaki zajmuje więcej pamięci, dzięki czemu można ograniczyć jego rozmiar początkowy na początku a następnie zapisać jako JPEG o rozmiarze oryginalna dachówka

+0

Ale to poprawia obraz, co prowadzi do niezbyt dobrej jakości obrazu. – Hannes

-1

Staraj się umieścić część:

 uiElement = null; 
     wbmp = null; 
     GC.Collect(); 

do wreszcie klauzuli;

+0

Przetestowałem to, ale to nie rozwiązało problemu, zobacz zaktualizowany post. – Hannes

+0

-1 ustawienie rzeczy na 'null' jest po prostu kultowym programowaniem. Nie przynosi ono żadnych korzyści, aw niektórych przypadkach może faktycznie przeszkadzać w zbieraniu śmieci. Ponadto, zraszanie wywołań do 'GC.Collect()' w kodzie produkcyjnym jest gigantyczną czerwoną flagą, której programista nie wiedział, co robią i pracuje raczej przeciwko niszczycielowi śmieci niż z nim. –

+0

@CodyGray Po prostu mówię, że jeśli poprawnie przeczytałeś oryginalne pytanie, nie sugerowałem pisania pustych liter i GC.Collect(), jest to część oryginalnego kodu, tylko zasugerowałem, aby przenieść go, by w końcu zablokować. Co więcej, przyjęta odpowiedź ma te same wartości zerowe i GC.Collect(), nie jestem pewna, dlaczego pominęłaś mnie specjalnie i czego ode mnie chcesz. –

0

mogę dostać taki sam wyjątek podczas konwersji UIElement do WriteableBitmap w TaskAgent. po prostu próbowałem tak wiele razy. i znalazłem rozwiązanie, które będzie działać.

wbmp.SaveJpeg(stream, width, heigth, 0, 60); 

można zmienić jakość do 60 po zapisaniu UIElement do strumienia. to znacznie zmniejsza zużycie pamięci. możesz tego spróbować.