20
votes

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?

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.Mare Infinitus
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.Hannes
Curious as to what happens if you finalize the canvas?Tebc
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.Hannes
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...J.T. Taylor

5 Answers

10
votes

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

0
votes

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.

0
votes

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

0
votes

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.

-1
votes

Try to put this part:

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

to finally clause;