WPF Image control not disposing source

核能气质少年 提交于 2021-02-04 18:30:09

问题


I've read multiple threads about this, but still cannot find anything that works.

I'm writing program for basically browsing database of images. I have a ListView with DataTemplate:

<DataTemplate>
        <Grid Width="Auto" Height="Auto" >
            <Image VerticalAlignment="Center" Source="{Binding IsAsync=True,
 Converter={StaticResource Converter},
 ConverterParameter={x:Static viewModel:SearchViewModel.MiniaturesHeight}}"
 Grid.RowSpan="2"  Stretch="None" Margin="5" 
Height="{Binding Source={StaticResource Locator}, Path=MiniaturesHeight}" 
Width="{Binding Source={StaticResource Locator}, Path=MiniaturesHeight}"
 RenderOptions.BitmapScalingMode="NearestNeighbor" />
             <TextBlock Text="{Binding Name}" Margin="5" />
        </Grid>
    </DataTemplate>

In converter I receive object and make URL from it's content. My problem is that I need to display 100 images per page, the whole database is for example 40k images. I would like to allow the user to click through all the pages without StackOveflowException. Unfortunately, each time I change page, memory usage increases and won't go down, even if I wait a long time.

Program itself uses around 60mb of RAM, but after changing page 5 times it's 150MB and steadily goes up.

This was my first converter:

  public class ObjectToUrl : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value == null)
                return DependencyProperty.UnsetValue;

            var obj = value as MyObject;

            return "base url" + obj.prop1;


        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return DependencyProperty.UnsetValue;
        }
    }

Then I've found, that WPF is caching by default all images passed to Image control using InternetExplorer caching options. This was a problem for me, since I wanted an easy way to update image on screen when other user would change something. So I changed my converter to use the most standard technique for that to disable caching:

  public class ObjectToUrl : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value == null)
                return DependencyProperty.UnsetValue;

            var obj = value as MyObject;

            var url = "base url" + obj.prop1;

            try
            {                
                var bmp = new BitmapImage();


                bmp.BeginInit();
                bmp.CacheOption = BitmapCacheOption.None;
                bmp.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
                bmp.UriSource = new Uri(url);
                bmp.EndInit();
                return bmp;
            }
            catch
            {
                return DependencyProperty.UnsetValue;
            }

        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return DependencyProperty.UnsetValue;
        }
    }

This works in exactly the same way, with exception, that if I remove and add item to list bound to ListView, it loads refreshed image.

I still have problem with memory leak. Any ideas?


回答1:


Increasing memory does not always indicate memory leak. Since .NET is garbage collected environment - GC decides itself when to run, based on its own heuristics. Part of that heuristics might be total amount of memory consumed by your application and total amount of available memory. Say you have 8GB available memory, and your application consumes 150MB. GC might think - why bother? After all, memory exists to be used, not to stay free all the time.

So to ensure that you have a memory leak - you might try to call GC.Collect periodically and see if that helps to reclaim memory. If yes - then you don't have a leak. If no - then you need to run profiler and figure out what's going on in more details. In any way - DO NOT leave that GC.Collect in your code after you figured out there is no memory leak. In very rare and specific cases it might be worth to leave it, but in general - just let GC do its job and reclaim memory when it sees fit. There is high chance it knows when to do this better than you.

The case with BitmapImage is a bit more complicated though. It's a wrapper around unmanaged resource, and all such wrappers should provide a Dispose method, so that caller can immediately reclaim unmanaged memory used by it (because, unlike managed memory - unmanaged usually can be reclaimed immediately, there is no garbage collector managing it).

For whatever historical reason, BitmapImage (BitmapSource) does not provide such method (at least not public, probably you can reach it via reflection). However, unmanaged resource pointer is wrapped into SafeHandle which has finalizer. In addition to that - BitmapSource calls GC.AddMemoryPressure (at least in modern .NET versions) to notify garbage collector that it holds X bytes of unmanaged memory.

That means GC knows exactly how much memory is consumed by BitmapImage, even though large part of this memory is unmanaged, and can account for that when deciding when to run garbage collection. When BitmapImage is collected - it's SafeHandle finalizer runs and unmanaged memory is reclaimed.

Long story short: it should be fine to just do nothing in your situation.




回答2:


Add this after EndInit():

bmp.Freeze();

Changed Handlers on Unfrozen Freezables may Keep Objects Alive



来源:https://stackoverflow.com/questions/49072438/wpf-image-control-not-disposing-source

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!