3

I have a very simple WPF app which is used to preview images in any given folder one image at a time. You can think of it as a Windows Image Viewer clone. The app has a PreviewKeyUp event used to load the previous or next image in the folder if the left arrow or right arrow key is pressed.

<Grid>
    <Image x:Name="CurrentImage" />
</Grid>

private void Window_PreviewKeyUp(object sender, KeyEventArgs e)
{
    switch (e.Key)
    {
        case Key.Left:
            DecreaseIndex();
            break;
        case Key.Right:
            IncreaseIndex();
            break;
    }

    var currentFile = GetCurrentFile();
    CurrentImage.Source = new BitmapImage(new Uri(currentFile));
}

The problem I'm trying to solve is that there is a large amount of memory bloat when loading multiple images until garbage collection occurs. You can see this in the screenshot I took of the app's memory usage. It's not uncommon for it to exceed 300 MB before garbage collection occurs.

screenshot

I tried wrapping the image in a using statement, but that doesn't work because BitmapImage doesn't implement IDisposable.

using (var image = new BitmapImage(new Uri(currentFile)))
{
    CurrentImage.Source = image;
}

What can I do to prevent memory bloat when loading multiple images into my app?

Halcyon
  • 14,631
  • 17
  • 68
  • 99

2 Answers2

7

When you say preview, you probably don't need the full image size. So besides of calling Freeze, you may also set the BitmapImage's DecodePixelWidth or DecodePixelHeight property.

I would also recommend to load the images directly from a FileStream instead of an Uri. Note that the online doc of the UriCachePolicy says that it is

... a value that represents the caching policy for images that come from an HTTP source.

so it might not work with local file Uris.

To be on the safe side, you could do this:

var image = new BitmapImage();

using (var stream = new FileStream(currentFile, FileMode.Open, FileAccess.Read))
{
    image.BeginInit();
    image.DecodePixelWidth = 100;
    image.CacheOption = BitmapCacheOption.OnLoad;
    image.StreamSource = stream;
    image.EndInit();
}

image.Freeze();
CurrentImage.Source = image;
Clemens
  • 123,504
  • 12
  • 155
  • 268
  • 1
    I would trust this answer over mine, it has a been a while since I had to figure out the quirks of BitmapImage and it seems to be a lot fresher in Clemens' mind. – Scott Chamberlain Jun 14 '17 at 13:42
5

Call .Freeze() on the bitmap object, this sets it in to a read-only state and releases some of the handles on it that prevents it from getting GC'ed.

Another thing you can do is you can tell the BitmapImage to bypass caching, the memory you see building up could be from the cache.

CurrentImage.Source = new BitmapImage(new Uri(currentFile), 
                                   new RequestCachePolicy(RequestCacheLevel.BypassCache));

Lastly, if there is not a lot of programs running on the computer putting memory pressure on the system .net is allowed to wait as long as it wants for a GC. Doing a GC is slow and lowers performance during the GC, if a GC is not necessary because no one is requesting the ram then it does not do a GC.

Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431