垃圾收集无法回收BitmapImage?

我有一个应用程序(WPF),它创建了大量的BitmapImages(如25000)。 看起来像框架使用一些内部逻辑,所以在创建之后消耗大约300 MB的内存(150虚拟和150物理)。 这些BitmapImages将添加到Image对象中,并将它们添加到Canvas中。 问题是,当我释放所有那些图像时,内存不会被释放。 我怎样才能释放回忆?

应用程序很简单:Xaml

          

代码隐藏

  const int size = 25000; BitmapImage[] bimages = new BitmapImage[size]; private void Button_Click(object sender, RoutedEventArgs e) { var paths = Directory.GetFiles(@"C:\Images", "*.jpg"); for (int i = 0; i < size; i++) { bimages[i] = new BitmapImage(new Uri(paths[i % paths.Length])); var image = new Image(); image.Source = bimages[i]; canvas.Children.Add(image); Canvas.SetLeft(image, i*10); Canvas.SetTop(image, i * 10); } } private void Remove_click(object sender, RoutedEventArgs e) { for (int i = 0; i < size; i++) { bimages[i] = null; } canvas.Children.Clear(); bimages = null; GC.Collect(); GC.Collect(); GC.Collect(); } 

这是添加图像后ResourceManager的屏幕截图 在此处输入图像描述

Wpf中有一个错误,我们被BitmapImage对象释放的地方所咬,除非你冻结它们。 http://blogs.msdn.com/b/jgoldb/archive/2008/02/04/finding-memory-leaks-in-wpf-based-applications.aspx是我们发现问题的原始页面。 它应该在Wpf 3.5 sp1中修复,但在某些情况下我们仍然可以看到它。 尝试更改这样的代码,看看是否是问题所在:

 bimages[i] = new BitmapImage(new Uri(paths[i % paths.Length])); bimages[i].Freeze(); 

我们现在经常冻结我们的BitmapImage对象,因为我们在分析器中看到其他实例,其中Wpf正在侦听BitmapImage上的事件,从而使图像保持活动状态。

如果Feeze()调用不是代码的明显修复,我强烈建议使用像RedGate Memory Profiler这样的分析器 – 它将跟踪一个依赖树,它将显示保持Image对象的内容。记忆。

对我有用的是:

  1. 将Image控件的ImageSource设置为null
  2. 在从UI中删除包含Image的控件之前运行UpdateLayout()。
  3. 确保在创建BitmapImage时冻结()并且没有对用作ImageSources的BitmapImage对象进行非弱引用。

我对每个Image的清理方法最终都像这样简单:

 img.Source = null; UpdateLayout(); 

我能够通过实验来实现这个目的,方法是保持一个WeakReference()对象的列表指向我创建的每个BitmapImage,然后检查WeakReferences上的IsAlive字段后应该清理它们以确认它们’ d实际上已经清理干净了。

所以,我的BitmapImage创建方法如下所示:

 var bi = new BitmapImage(); using (var fs = new FileStream(pic, FileMode.Open)) { bi.BeginInit(); bi.CacheOption = BitmapCacheOption.OnLoad; bi.StreamSource = fs; bi.EndInit(); } bi.Freeze(); weakreflist.Add(new WeakReference(bi)); return bi; 

我按照AAAA给出的答案。 导致内存填满的原始代码是:

 if (overlay != null) overlay.Dispose(); overlay = new Bitmap(backDrop); Graphics g = Graphics.FromImage(overlay); 

插入AAAA的代​​码块,C#添加“using System.Threading;” 和VB添加“Imports System.Threading”:

 if (overlay != null) overlay.Dispose(); //--------------------------------------------- code given by AAAA Thread t = new Thread(new ThreadStart(delegate { Thread.Sleep(500); GC.Collect(); })); t.Start(); //-------------------------------------------- \code given by AAAA overlay = new Bitmap(backDrop); Graphics g = Graphics.FromImage(overlay); 

重复循环此块现在可以实现稳定且低内存占用。 此代码使用Visual Studio 2015社区。

这仍然是一个数组

 BitmapImage[] bimages = new BitmapImage[size]; 

数组是连续的固定长度数据结构,一旦为整个数组分配了内存,就无法回收它的一部分。 尝试使用其他数据结构(如LinkedList )或其他更适合您的情况

我只是告诉我有关回收BitmapImage内存的经验。 我使用.Net Framework 4.5。
我创建简单的WPF应用程序并加载一个大的图像文件。 我尝试使用以下代码从内存中清除Image:

 private void ButtonImageRemove_Click(object sender, RoutedEventArgs e) { image1.Source = null; GC.Collect(); } 

但它没有用。 我也尝试了其他解决方案,但我没有得到答案。 经过几天的挣扎,我发现如果按两次按钮,GC将释放内存。 然后我只需编写此代码,在点击按钮后几秒钟调用GC收集器。

 private void ButtonImageRemove_Click(object sender, RoutedEventArgs e) { image1.Source = null; System.Threading.Thread thread = new System.Threading.Thread(new System.Threading.ThreadStart(delegate { System.Threading.Thread.Sleep(500); GC.Collect(); })); thread.Start(); } 

这段代码刚刚在DotNetFr 4.5中测试过。 也许你必须为较低的.Net Framework冻结BitmapImage对象。
编辑
除非布局更新,否则此代码不起作用。 我的意思是如果父控件被删除,GC无法收回它。