WPF图像控件不处理源

时间:2018-03-02 15:39:16

标签: c# wpf xaml data-binding

我已经阅读过有关此问题的多个主题,但仍无法找到有效的内容。

我正在编写基本浏览图像数据库的程序。我有一个带有DataTemplate的ListView:

<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>

在转换器中,我会收到对象并根据其内容制作网址。我的问题是我需要每页显示100个图像,整个数据库例如是40k图像。我想允许用户在没有StackOveflowException的情况下点击所有页面。不幸的是,每次我更换页面时,即使我等了很长时间,内存使用量也会增加并且不会下降。

程序本身使用大约60mb的RAM,但在改变页面5次后,它的150MB并且稳步上升。

这是我的第一个转换器:

  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;
        }
    }

然后我发现,WPF默认使用InternetExplorer缓存选项缓存传递给Image控件的所有图像。这对我来说是一个问题,因为当其他用户改变某些东西时,我想要一种简单的方法来更新屏幕上的图像。所以我改变了我的转换器以使用最标准的技术来禁用缓存:

  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;
        }
    }

这种方式完全相同,例外情况是,如果我删除并将项目添加到绑定到ListView的列表中,它会加载刷新的图像。

我仍然遇到内存泄漏问题。有什么想法吗?

2 个答案:

答案 0 :(得分:4)

增加内存并不总是表明内存泄漏。由于.NET是垃圾收集环境,因此GC根据自己的启发式决定何时运行。部分启发式可能是应用程序消耗的总内存量和可用内存总量。假设你有8GB可用内存,你的应用程序消耗150MB。 GC可能会想 - 为什么要这么麻烦?毕竟,存在使用的存在,而不是一直保持自由。

因此,为了确保您有内存泄漏 - 您可能会尝试定期调用GC.Collect,看看这是否有助于回收内存。如果是 - 那么你没有泄漏。如果不是 - 那么你需要运行探查器并在更多细节中弄清楚发生了什么。无论如何 - 在您发现没有内存泄漏之后,请不要在代码中留下GC.Collect。在非常罕见和特定的情况下,可能值得留下它,但总的来说 - 只要让GC完成它的工作并在它认为合适时回收内存。很有可能它知道什么时候比你更好。

BitmapImage的情况虽然有点复杂。它是非托管资源的包装器,所有这些包装器都应提供Dispose方法,以便调用者可以立即回收它使用的非托管内存(因为,与托管内存不同 - 通常不受管理的可以立即回收,没有垃圾收集器管理它。)

无论历史原因如何,BitmapImageBitmapSource)都没有提供这样的方法(至少不公开,可能你可以通过反思来达到它)。但是,非托管资源指针被包装到具有终结器的SafeHandle中。除此之外 - BitmapSource调用GC.AddMemoryPressure(至少在现代.NET版本中)通知垃圾收集器它拥有X字节的非托管内存。

这意味着GC确切地知道BitmapImage消耗了多少内存,即使这个内存的大部分是非托管的,并且可以在决定何时运行垃圾收集时考虑到这一点。收集BitmapImage时,它会运行SafeHandle终结器并回收非托管内存。

长话短说:在你的情况下做任何事都应该没事。

答案 1 :(得分:0)

EndInit()之后添加此内容:

bmp.Freeze();

Changed Handlers on Unfrozen Freezables may Keep Objects Alive