WPF 图像控件不处理源

Krz*_*nek 5 c# data-binding wpf xaml

我已经阅读了有关此的多个线程,但仍然找不到任何有效的内容。

我正在编写基本上浏览图像数据库的程序。我有一个带有 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>
Run Code Online (Sandbox Code Playgroud)

在转换器中,我接收对象并根据其内容创建 URL。我的问题是我需要每页显示 100 个图像,例如整个数据库有 40k 个图像。我想允许用户单击所有页面而不会出现 StackOveflowException。不幸的是,每次我更改页面时,内存使用量都会增加并且不会下降,即使我等待很长时间也是如此。

程序本身使用大约 60MB 的 RAM,但在更改页面 5 次后,它的 RAM 达到 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;
        }
    }
Run Code Online (Sandbox Code Playgroud)

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

  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;
        }
    }
Run Code Online (Sandbox Code Playgroud)

这以完全相同的方式工作,除了例外,如果我删除项目并将其添加到绑定到 ListView 的列表,它会加载刷新的图像。

我仍然有内存泄漏的问题。有任何想法吗?

Evk*_*Evk 5

增加内存并不总是意味着内存泄漏。由于 .NET 是垃圾收集环境 - GC 根据其自身的启发式自行决定何时运行。该启发法的一部分可能是应用程序消耗的内存总量和可用内存总量。假设您有 8GB 可用内存,而您的应用程序消耗 150MB。GC 可能会想——何苦呢?毕竟,内存的存在是为了被使用,而不是一直保持空闲。

因此,为了确保存在内存泄漏 - 您可以尝试GC.Collect定期调用并查看是否有助于回收内存。如果是的话 - 那么你就没有泄漏。如果没有 - 那么您需要运行分析器并更详细地了解发生了什么。无论如何 -GC.Collect在发现没有内存泄漏后,不要将其留在代码中。在非常罕见和特定的情况下,可能值得保留它,但一般来说 - 只需让 GC 完成其工作并在认为合适时回收内存即可。它很有可能比你知道什么时候应该做得更好。

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

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

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

长话短说:在你的情况下,什么也不做应该没问题。