Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OutOfMemoryException @ WriteableBitmap @ background agent

I have a Windows Phone 8 App, which uses Background agent to:

  1. Get data from internet;
  2. Generate image based on a User Control that uses the data from step 1 as data source;
  3. In the User Control I have Grid & StackPanel & some Text and Image controls;
  4. When some of the Images use local resources from the installation folder (/Assets/images/...) One of them which I used as a background is selected by user from the phone's photo library, so I have to set the source using C# code behind.

However, when it runs under the background, it get the OutOfMemoryException, some troubleshooting so far:

  1. When I run the process in the "front", everything works fine;
  2. If I comment out the update progress, and create the image directly, it also works fine;
  3. If I don't set the background image, it also works fine;
  4. The OutOfMemoryException was thrown out during var bmp = new WriteableBitmap(480, 800);
    I already shrink the image size from 1280*768 to 800*480, I think it is the bottom line for a full screen background image, isn't it?
  5. After some research, I found out this problem occurs because it exceeded the 11 MB limitation for a Periodic Task.
  6. I tried use the DeviceStatus.ApplicationCurrentMemoryUsage to track the memory usage:

    -- the limitation is 11,534,336 (bit)

    -- when background agent started, even without any task in it, the memory usage turns to be 4,648,960

    -- When get update from internet, it grew up to 5,079,040

    -- when finished, it dropped back to 4,648,960

    -- When the invoke started (to generate image from the User Control), it grew up to 8,499,200

Well, I guess that's the problem, there is little memory available for it to render the image via WriteableBitmap.

Any idea how to work out this problem?

Is there a better method to generate an image from a User Control / or anything else?

Actually the original image might only be 100 kb or around, however, when rendering by WriteableBitmap, the file size (as well as the required memory size I guess) might grew up to 1-2MB.

Or can I release the memory from anywhere?

==============================================================

BTW, when this Code Project article says I can use only 11MB memory in a Periodic Task;

However, this MSDN article says that I can use up to 20 MB or 25MB with Windows Phone 8 Update 3; Which is correct? And why am I in the first situation?

==============================================================

Edit:

Speak of the debugger, it also stated in the MSDN article:

When running under the debugger, memory and timeout restrictions are suspended.

But why would I still hit the limitation?

==============================================================

Edit:

Well, I found something seems to be helpful, I will check on them for now, suggestions are still welcome.

http://writeablebitmapex.codeplex.com/

http://suchan.cz/2012/07/pro-live-tiles-for-windows-phone/

http://notebookheavy.com/2011/12/06/microsoft-style-dynamic-tiles-for-windows-phone-mango/

==============================================================

The code to generate the image:

Deployment.Current.Dispatcher.BeginInvoke(() =>
{
    var customBG = new ImageUserControl();
    customBG.Measure(new Size(480, 800));
    var bmp = new WriteableBitmap(480, 800); //Thrown the **OutOfMemoryException**
    bmp.Render(customBG, null);
    bmp.Invalidate();
    using (var isf = IsolatedStorageFile.GetUserStoreForApplication())
    {
        filename = "/Shared/NewBackGround.jpg";
        using (var stream = isf.OpenFile(filename, System.IO.FileMode.OpenOrCreate))
        {
            bmp.SaveJpeg(stream, 480, 800, 0, 100);
        }
    }
}

The XAML code for the ImageUserControl:

<UserControl blabla... d:DesignHeight="800" d:DesignWidth="480">
    <Grid x:Name="LayoutRoot">
    <Image x:Name="nBackgroundSource" Stretch="UniformToFill"/>

    //blabla...
    </Grid>
</UserControl>

The C# code behind the ImageUserControl:

public ImageUserControl()
{
    InitializeComponent();
    LupdateUI();
}

public void LupdateUI()
{
    DataInfo _dataInfo = new DataInfo();
    LayoutRoot.DataContext = _dataInfo;
    try
    {
        using (var isoStore = IsolatedStorageFile.GetUserStoreForApplication())
        {
            using (var isoFileStream = isoStore.OpenFile("/Shared/BackgroundImage.jpg", FileMode.Open, FileAccess.Read))
            {
                BitmapImage bi = new BitmapImage();
                bi.SetSource(isoFileStream);
                nBackgroundSource.Source = bi;
            }
        }
    }
    catch (Exception) { }
}

When DataInfo is another Class within the settings page that hold data get from the internet:

public class DataInfo
{
    public string Wind1 { get { return GetValueOrDefault<string>("Wind1", "N/A"); } set { if (AddOrUpdateValue("Wind1", value)) { Save(); } } }
    public string Wind2 { get { return GetValueOrDefault<string>("Wind2", "N/A"); } set { if (AddOrUpdateValue("Wind2", value)) { Save(); } } }
    //blabla...
}
like image 901
Max Meng Avatar asked Dec 25 '13 07:12

Max Meng


2 Answers

If I comment out the update progress, and create the image directly, it also works fine I think you should focus on that part. It seems to indicate some memory isn't freed after the update. Make sure all the references used during the update process go out of scope before rendering the picture. Forcing a garbage collection can help too:

GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect(); // Frees the memory that was used by the finalizers

Another thing to consider is that the debugger is also using a lot of memory. Do a real test by compiling your project in "Release" mode and deploying on a phone to make sure you're running out of memory.

Still, I have already been in that situation so I know it may not be enough. The point is: some libraries in the .NET Framework are loaded lazily. For instance, if your update process involves downloading some data, then the background agent will load the network libraries. Those libraries can't be unloaded and will waste some of your agent's memory. That's why, even by freeing all the memory you used during the update process, you won't reach back the same amount of free memory you had when starting the background agent. Seeing this, what I did in one of my app was to span the workload of the background agent across two executions. Basically, when the agents executes:

  • Check in the isolated storage if there's pending data to be processed. If not, just execute the update process and store all needed data in the isolated storage
  • If there is pending data (that is, in the next execution), generate the picture and clear the data

It means the picture will be generated only once every hour instead of once every 30 minutes, so use this workaround only if everything else fail.

like image 83
Kevin Gosse Avatar answered Nov 19 '22 06:11

Kevin Gosse


The larger memory limit is for background audio agents, it clearly states that in the documentation. You're stuck with 11 MB, which can really be a big pain when you're trying to do something smart with pictures in the background.

480x800 adds a MB to your memory because it takes 4 bytes for every pixels, so in the end it's around 1.22MB. When compressed in JPEG, then yes - it makes sense that it's only around 100KB. But whenever you use WriteableBitmap, it gets loaded into memory.

One of the things you could try before forcing the GC.Collect as mentioned in another answer is to null things out even before they go out of scope - whether it's a BitmapImage or a WriteableBitmap. Other than that you can try removing the Image object from the Grid programmatically when you're done and setting the Source of it also to be null.

Are there any other WriteableBitmap, BitmapImage or Image objects you're not showing us?

Also, try without the debugger. I've read somewhere that it adds another 1-2MB which is a lot when you have only 11 MB. Although, if it crashes so quickly with the debugger, I wouldn't risk it even if it suddenly seams OK without the debugger. But just for testing purposes you can give it a shot.

Do you need to use the ImageUserControl? Can you try doing step by step creating Image and all the other objects, without the XAML, so you can measure memory in each and every step to see at which point it goes through the roof?

like image 28
Igor Ralic Avatar answered Nov 19 '22 08:11

Igor Ralic