WriteableBitmap Memory Leak?
Asked Answered
A

5

20

i am using the code below to create a live tile, based on an UI element. It renders the uiElement on a WriteableBitmap, saves the bitmap + returns the filename. This method is run in a windows phone background task agent an i am running into memory limits.

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

I did some testing and the problem is: This methods leaks memory, but i do not know why/where?!

I also did some test runs - before first run into this method:

Microsoft.Phone.Info.DeviceStatus.ApplicationCurrentMemoryUsage
7.249.920 Bytes

This is ok, since the debugger is attached, which uses about 2 MB memory.

Doing some more runs of this method (set back to run method again in 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

So the memory, used by this method increases.

But why? In my opinion there are no references that prevent the objects from getting collected.

UPDATE on 04.05.2013

Hi,

thank you for all your answers! As suggested, i reduced the code + finnally was able to reproduce the problem in a few lines of code.

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

After starting the App: ApplicationCurrentMemoryUsage: "11 681 792 Bytes"

1 Iteration - ApplicationCurrentMemoryUsage: "28 090 368 Bytes"

5 Iterations - ApplicationCurrentMemoryUsage: "77 111 296 Bytes"

20 Iterations - ApplicationCurrentMemoryUsage: "260 378 624 Bytes"

After 23 Iterations: Out of Memory Exception. Ln.: var writeableBitmap = new WriteableBitmap(rectangle, rectangle.RenderTransform);

Only by commenting out the line "Fill = solidColorBrush", the CreateImage() method was called 100 times without any problems - after the 100th iteration, memory usage was about "16 064 512 Bytes".

So it seems the problem is the brush!! When used to fill an UI element, and later this UI elemnt is renderes on a writeable bitmap, the bitmap never gets garbage collected.

Of course this makes no sense in my opinion. The brush runns out of scope so it simply should be garbage collected too! (Setting the brush to null after it was used did not change anything)

Many of my UI elements use a brush for filling, so i cannot simply remove the usage of brushes. What do you think about this issue?

Allotropy answered 5/2, 2013 at 15:20 Comment(5)
You should note that there is usually no reason to call GC.Collect() directly. It can be harmful, freeze your application and has a performance hit.Euphemize
That's true, it was more some kind of 'desperate attempt', in fact it does not do anything and also has no influence on the memory consumption.Allotropy
Curious as to what happens if you finalize the canvas?Lucania
The canvas is only used to be rendered on the WriteableBitmap, it is no longer used after this method and never shown in an UI, since this is a background task. But you are right, setting it to null is not "good practic" but it has no impact on the behaviour at all.Allotropy
Can you narrow down the cause of the leak any more by commenting out lines of code, and then checking the leak. First, just render and invalidate but don't write to storage? Leak still there? Then, just write the jpeg to the isolated storage without first rendering and invalidating. I would guess some system process is holding a reference to either the front or back buffer of the WriteableBitmap...Whencesoever
A
10

The Problem is that rectangle.RenderTransform is an instance of an object and if you set writableBitmap to null the rectangle.RenderTransform Object is still alive and holds the rectangle in the memory... so the solution is to edit the code as follows:

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

see the memory screenshots...

before:

memory without setting rectangle.RenderTransform to null

after:

memory with setting rectangle.RenderTransform to null

Attrition answered 30/5, 2013 at 13:53 Comment(2)
How do you get these memory analysis? I'm using VS 2013 Preview / Ultimate.Hedgehog
You can open this perspective through: DEBUG > Start Performance Analysis (Alt+F2)Attrition
S
0

Your finally in the try catch should be wbmp = null for a kick off.

And you are rendering it, which means you are attaching that object to an outside (the function) list. Therefore no amount of GC.Collect will actually collect it as it's still 'in play'.

Set the uielement source to null, that may get rid of the attached reference why the GC is ignoring it.

Sikorski answered 20/2, 2013 at 20:45 Comment(1)
thanks, i tried both but it did not help - please see the updated posting - the problem still exists.Allotropy
F
0

You can limit the size of writable bitmap image width X height to smaller size as you when increase its size it takes more memory so you can limit its initial size at first then save as jpeg with original tile size

Fungous answered 21/2, 2013 at 10:59 Comment(1)
But this upscales the image, which leads to not that good image qulityAllotropy
K
0

i get same exception when convert uielement to writeablebitmap in taskagent. just tried so many times.i found a soluction get be worked.

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

you can change the quality to 60 when you save uielement to stream. it well reduce memory useage. you can try it.

Kinsley answered 13/12, 2013 at 4:47 Comment(0)
P
-1

Try to put this part:

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

to finally clause;

Pledge answered 24/2, 2013 at 2:20 Comment(3)
I tested this but it does not solve the problem, please see updated posting.Allotropy
-1 setting things to null is just cargo-cult programming. It does nothing useful, and in some cases can actually interfere with garbage collection. Furthermore, sprinkling calls to GC.Collect() in production code is a giant red flag that a programmer didn't know what they were doing and is working against the garbage collector rather than with it.Mucosa
@CodyGray I'm just saying that If you read original question properly, I didn't suggest to write nulls and GC.Collect(), it's part of original code, I only suggested to move it to finally block. More to it, accepted answer has the same nulls and GC.Collect(), not sure, why did you minus me specifically and what do you even want from me.Pledge

© 2022 - 2024 — McMap. All rights reserved.