垃圾收集无法回收BitmapImage?

时间:2012-06-26 07:43:23

标签: c# wpf bitmapimage

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

应用程序很简单: XAML

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Canvas x:Name="canvas" Grid.ColumnSpan="2"></Canvas>
        <Button Content="Add" Grid.Row="1" Click="Button_Click"/>
        <Button Content="Remove" Grid.Row="1" Grid.Column="1" Click="Remove_click"/>
    </Grid>

代码隐藏

        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的屏幕截图 enter image description here

5 个答案:

答案 0 :(得分:6)

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对象,因为我们在Profiler中看到其他实例,其中Wpf正在侦听BitmapImage上的事件,从而使图像保持活动状态。

如果Feeze()调用对您的代码不是一个明显的修复,我强烈建议您使用一个分析器,例如RedGate Memory Profiler - 它将跟踪一个依赖树,它会告诉您它是什么让您保持内存中的图像对象。

答案 1 :(得分:2)

对我有用的是:

  1. 将Image控件的ImageSource设置为null
  2. 在从UI中删除包含图像的控件之前运行UpdateLayout()。
  3. 确保在创建BitmapImage时冻结(),并且没有对用作ImageSources的BitmapImage对象进行非弱引用。
  4. 每张图片的清理方法最终都是这样简单:

    img.Source = null;
    UpdateLayout();
    

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

    所以,我的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;
    

答案 2 :(得分:2)

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

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

使用System.Threading插入AAAA的代​​码块,C#add&#34;&#34;和VB添加&#34; Imports System.Threading&#34;:

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社区。

答案 3 :(得分:1)

这仍然是一个数组

BitmapImage[] bimages = new BitmapImage[size];

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

答案 4 :(得分:1)

我只是告诉我有关回收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无法收回它。