1
votes

I am building a small Windows Phone 8 app (a Christian-Orthodox calendar) which has a background agent which should update the live tile. The app will require access to the contacts in the phone so I opted out of internet access so backend tile generation is, at least now out of question. I personally would not trust an app that has access to my contacts AND to internet.

Recently my scheduled agent (which generates three PNGs) started OutOfMemoryException-ing on me. Consistently. I've used DeviceStatus to query and debug its behavior.

It's hard to call this a memory leak since between all three tile generations if I call GC.Collect it won't throw OutOfMemoryException. If it were a true memory leak some (large and/or many) objects would remain referenced by other live/root objects and no amount of GC.Collect will help. In my case GC.Collect WILL help. I can continue using GC.Collect but I feel dirty doing so.

As I'm developing the app free and open-source you can view in detail all the code of the project at the current state of development at http://orthodoxcalendar.codeplex.com

The tile generation consists of taking a background and overlaying two other images on that background. Basically for each of the three PNGs generated I do

var bytes1 = (byte[])resourceManager.GetObject(resourceName1);
var stream1 = new MemoryStream(bytes);

var bytes2 = (byte[])resourceManager.GetObject(resourceName2);
var stream2 = new MemoryStream(bytes);

var bytes3 = (byte[])resourceManager.GetObject(resourceName3);
var stream3 = new MemoryStream(bytes);

var writeableBitmap1 = BitmapFactory.New(size.Width, size.Height).FromStream(stream1); // background
var writeableBitmap2 = BitmapFactory.New(size.Width, size.Height).FromStream(stream2); // first overlay
var writeableBitmap3 = BitmapFactory.New(size.Width, size.Height).FromStream(stream3); // second overlay

writeableBitmap1.Blit(new Point(0, 0), writeableBitmap2, new Rect(0, 0, width2, height2), Colors.White, BlendMode.Alpha);
writeableBitmap1.Blit(new Point(0, 0), writeableBitmap3, new Rect(0, 0, width3, height3), Colors.White, BlendMode.Alpha);
writeableBitmap1.DrawText("Some text", new Point(5, 139), Color.Black, 17);
writeableBitmap1.Invalidate(); // flatten things

using(var outputStream = new WhateverStream())
{
  PNGWriter.Write(writeableBitmap1, outputStream);
}

writeableBitmap1.SetSource(new MemoryStream(MiscData.MinimumPng)); // set the writeable bitmap to a 1x1 transparent PNG to, hopefully, force it to release unamanaged memory or other stuff
writeableBitmap2.SetSource(new MemoryStream(MiscData.MinimumPng));
writeableBitmap3.SetSource(new MemoryStream(MiscData.MinimumPng));

stream1.Dispose();
stream2.Dispose();
stream3.Dispose();

The code, if you'll check out the project, is not exactly like above since I've wrapped almost all dependencies in adapters and extracted interfaces. Across many assemblies. The above code is the simplified version which just shows, what I consider to be, the relevant code lines.

A few explanations for the code above :

  • all this code is run in the background agent inside a Dispatcher.BeginInvoke since you can't seem to manipulate a WritableBitmap on any other thread than the UI thread
  • The PNG data is stored in another assembly as resx. I know this fattens the assembly but I need this to reuse it across platforms as the assembly is a PCL
  • Creating the WriteableBitmap directly using a byte array seems to fail in a mysterious way so I'm wrapping it in a MemoryStream and somehow, this way, it works
  • The PNG writer is taken from ToolStack.
  • It's not feasible to pre-generate the images since there are multiple versions of "first overlay", "second overlay" and, mostly the "Some text". It would mean tens of thousands of images, at least.

The heart of the question : Am I doing something awfully wrong that I'm not aware of? The only thing that pops in my mind is that JPEGs are generated faster and with less memory consumption but they won't have transparency which I desire. Can this be actually called a memory leak?


LATER EDIT : It seems that after some more debugging it changed its behavior from the one above to a true memory leak. I switched from PNG generation to JPEG generation and the memory is lower now. The input images are still PNG but at the other end a JPEG will be spit. The memory footprint went several megabytes below the previous threshold(s).


SECOND EDIT : I put the logic in a 10.000 repeat loop on a button and there doesn't seem too much memory consumption. I am beginning to think that there isn't really a memory leak but just higher memory consumption during the generation and that's enough to bring the fragile agent down.

1
Thanks, yes, I previously had seen them but I don't have a transform of any kind (which would resemble the first post), even "worse", as I wrote in the second edit, I don't seem to have a "true" leak because forcing the garbage collection keeps the footprint down. Only when I don't force the garbage collection the runtime will throw out of memory exception instead of (as far as it should have?) doing its "own" collection.Andrei Rînea
I just don't get it why it works with explicit GC.Collect() and not without it (hits the memory cap and blows up with OOME).Andrei Rînea
I see no memory leak, just that you've forgotten that the background process have a very small memory limit (10mb or so), which includes everything, including the data footprint for your assembly inclusion, resulting in you having very very little memory at your disposal. Also, the PNG generation is going to be really inefficient memory and CPU wise, so I would advice against it.Claus Jørgensen
Additionally, I would recommend you pick one of the existing tile templates, like the IconicTemplate and use that for your tiles, the double-wide IconicTile is really good for showing information, such as calendar information.Claus Jørgensen

1 Answers

1
votes

In doing a similar thing I've had to explicitly set the writeablebitmaps to null (even though should be unnecessary) before calling GC.Collect.

Additionally, it may be better to create and destroy (and collect) each of the images in turn, rather than creating them all and then destroying them all. This will help with the overhead at any one point.

Also note that when tracking the memory use in the debugger, the debugger adds about 3mb of overhead that you won't see when live.