我已经阅读过有关此问题的多个主题,但仍无法找到有效的内容。
我正在编写基本浏览图像数据库的程序。我有一个带有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的列表中,它会加载刷新的图像。
我仍然遇到内存泄漏问题。有什么想法吗?
答案 0 :(得分:4)
增加内存并不总是表明内存泄漏。由于.NET是垃圾收集环境,因此GC根据自己的启发式决定何时运行。部分启发式可能是应用程序消耗的总内存量和可用内存总量。假设你有8GB可用内存,你的应用程序消耗150MB。 GC可能会想 - 为什么要这么麻烦?毕竟,存在使用的存在,而不是一直保持自由。
因此,为了确保您有内存泄漏 - 您可能会尝试定期调用GC.Collect
,看看这是否有助于回收内存。如果是 - 那么你没有泄漏。如果不是 - 那么你需要运行探查器并在更多细节中弄清楚发生了什么。无论如何 - 在您发现没有内存泄漏之后,请不要在代码中留下GC.Collect
。在非常罕见和特定的情况下,可能值得留下它,但总的来说 - 只要让GC完成它的工作并在它认为合适时回收内存。很有可能它知道什么时候比你更好。
BitmapImage
的情况虽然有点复杂。它是非托管资源的包装器,所有这些包装器都应提供Dispose
方法,以便调用者可以立即回收它使用的非托管内存(因为,与托管内存不同 - 通常不受管理的可以立即回收,没有垃圾收集器管理它。)
无论历史原因如何,BitmapImage
(BitmapSource
)都没有提供这样的方法(至少不公开,可能你可以通过反思来达到它)。但是,非托管资源指针被包装到具有终结器的SafeHandle
中。除此之外 - BitmapSource
调用GC.AddMemoryPressure
(至少在现代.NET版本中)通知垃圾收集器它拥有X字节的非托管内存。
这意味着GC确切地知道BitmapImage
消耗了多少内存,即使这个内存的大部分是非托管的,并且可以在决定何时运行垃圾收集时考虑到这一点。收集BitmapImage
时,它会运行SafeHandle
终结器并回收非托管内存。
长话短说:在你的情况下做任何事都应该没事。
答案 1 :(得分:0)