7

我有一个应用程序(WPF),它创建了大量的 BitmapImages(比如 25000)。似乎框架使用了一些内部逻辑,因此在创建后消耗了大约 300 mb 的内存(150 个虚拟和 150 个物理)。这些 BitmapImage 被添加到 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添加图片后的截图 在此处输入图像描述

4

5 回答 5

6

Wpf 中有一个错误,我们被 BitmapImage 对象不释放的地方咬住了,除非你冻结它们。https://www.jawahar.tech/home/finding-memory-leaks-in-wpf-based-applications是我们发现问题的原始页面。它应该已在 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 对象保留在其中的原因记忆。

于 2012-06-26T08:10:20.490 回答
2

对我有用的是:

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

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

img.Source = null;
UpdateLayout();

我能够通过实验来实现这一点,方法是保留一个包含指向我创建的每个 BitmapImage 的 WeakReference() 对象的列表,然后在应该清理它们之后检查 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;
于 2013-06-11T04:30:54.863 回答
2

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

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

插入AAAA的代​​码块,C#添加“using System.Threading;” 和 VB 添加“导入 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 社区工作。

于 2015-07-24T20:34:47.193 回答
1

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

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 无法回收它。

于 2013-09-28T09:20:31.630 回答
1

这仍然是一个数组

BitmapImage[] bimages = new BitmapImage[size];

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

于 2012-06-26T07:58:56.687 回答