【问题标题】:Disposing of a BitmapImage after its been frozen冻结后处理 BitmapImage
【发布时间】:2017-06-07 09:45:30
【问题描述】:

我用这个函数创建了一个图像:

private BitmapImage LoadImage(byte[] imageData)
{
    if (imageData == null || imageData.Length == 0) return null;
    var image = new BitmapImage();
    using (var mem = new MemoryStream(imageData))
    {
        mem.Position = 0;
        image.BeginInit();
        image.CreateOptions = BitmapCreateOptions.PreservePixelFormat;
        image.CacheOption = BitmapCacheOption.OnLoad;
        image.UriSource = null;
        image.StreamSource = mem;
        image.EndInit();
    }
    image.Freeze();
    return image;
}

当我尝试处理它时:

myImage.StreamSource.Close();
myImage.StreamSource.Dispose();

// Throws an exception since its frozen to read only
//myImage.StreamSource = null;

GC.Collect();

垃圾收集器不会收集它。可能是因为我无法将其设置为null

我怎样才能处理这个BitmapImage,使它不会在内存中存活更长的时间?

【问题讨论】:

    标签: c# wpf image bitmapimage


    【解决方案1】:

    您已经处置了 StreamSource,因为您在 using 语句中创建了 MemoryStream,该语句在离开代码块时处置。 BitmapImage 本身是只托管的,不需要处理。

    您确定它没有被垃圾收集器清理吗?我有一个项目通过BitmapImages 和BitmapCacheOption.OnLoad 赚了很多钱,但我从未见过它的内存泄漏。

    (更新)在 WPF 中测试:(更新 2)需要添加另一轮垃圾回收。出于某种原因,您必须调用它两次才能释放数组。

    private async void Button_Click(object sender, RoutedEventArgs e)
    {
        WeakReference test = this.TestThing();
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
        GC.WaitForPendingFinalizers();
    
        Debug.WriteLine(test.IsAlive); // Returns false
    }
    
    private WeakReference TestThing()
    {
        byte[] imageData = File.ReadAllBytes(@"D:\docs\SpaceXLaunch_Shortt_3528.jpg");
    
        var image = new BitmapImage();
        using (var mem = new MemoryStream(imageData))
        {
            image.BeginInit();
            image.CreateOptions = BitmapCreateOptions.PreservePixelFormat;
            image.CacheOption = BitmapCacheOption.OnLoad;
            image.UriSource = null;
            image.StreamSource = mem;
            image.EndInit();
        }
    
        image.Freeze();
    
        return new WeakReference(image);
    }
    

    【讨论】:

    • 我可以用这段代码确认字节数组没有被释放,在某处仍有引用。您可以通过向其添加WeakReference 并检查IsAlive 属性来自行测试它。我在发布模式下执行此操作,没有附加调试器。
    • 添加了一些测试代码。在这种情况下,我不确定为什么 IsAlive 对你来说是 true。如果我在同一个函数和调试模式下进行 GC 调用,它会返回 true,但如果它在同一个函数中并且在发布模式下,它仍然是 false。也许您可以分享您使用的确切代码?
    • @Stuart 我在跟踪传入内存流的字节数组时也得到了相同的结果。
    • @Stuart 我的错,我需要添加另一轮垃圾收集才能真正起作用。
    【解决方案2】:

    我们可以使用以下代码调查问题:

    public static void Main()
    {
        var readAllBytes = File.ReadAllBytes(@"SomeBitmap.bmp");
        var wr = new WeakReference(readAllBytes);
        var result = LoadImage(readAllBytes);
        readAllBytes = null;
    
        //result.StreamSource = null;
    
        result = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine($"IsAlive: {wr.IsAlive}");
        Console.ReadLine();
    }
    
    private static BitmapImage LoadImage(byte[] imageData)
    {
        if (imageData == null || imageData.Length == 0) return null;
        var image = new BitmapImage();
        using (var mem = new MemoryStream(imageData))
        {
            mem.Position = 0;
            image.BeginInit();
            image.CreateOptions = BitmapCreateOptions.PreservePixelFormat;
            image.CacheOption = BitmapCacheOption.OnLoad;
            image.UriSource = null;
            image.StreamSource = mem;
            image.EndInit();
        }
        image.Freeze();
        return image;
    }
    

    我尝试了很多不同的缓存设置,我找不到释放字节数组的方法,它看起来像 WPF 中的一个错误。

    如您所见,在 2 次 GC 收集后,字节数组被释放。

    编辑 1:通过删除 Freeze 来简化 BitmapImageInit 方法确实释放了字节数组:

    private static BitmapImage LoadImage(byte[] imageData)
    {
        if (imageData == null || imageData.Length == 0) return null;
        var image = new BitmapImage();
        using (var mem = new MemoryStream(imageData))
        {
            mem.Position = 0;
            image.CreateOptions = BitmapCreateOptions.PreservePixelFormat;
            image.UriSource = null;
            image.StreamSource = mem;
        }
        return image;
    }
    

    编辑 2:正如其他人所指出的,字节数组在 2 轮垃圾收集后被释放,我更新了上面的示例。我发现这非常有用,它表明该框架不是黑匣子。

    【讨论】:

    • 奇怪的是,如果我批量进行。除非我再做大量图像,否则它也不会发布。
    • 我删除了冻结,现在当我创建一个新图像并调用 EndInit() 时会发生垃圾收集。
    • 谢谢@John,我已经验证了你的发现并更新了我的答案以包含它=)
    • 创建一个全新的图像来强制垃圾收集器并不是一个理想的解决方案。需要弄清楚是什么在调用以前没有发生的集合。 @斯图尔特
    • 啊,我明白有什么不同了。在测试时,我碰巧在两个地方调用了 GC.Collect()。如果你调用它一次,它不会释放它,但下一轮会得到它。我认为这就是为什么我在我的应用程序中看不到内存泄漏的原因;内存不会立即被清理,但下一个会得到它。
    【解决方案3】:

    就我而言,我需要在服务器上存储为字节数组的一堆大型平面图之间切换。一旦所有这些都加载到内存中并在它们之间进行了一些切换之后,我就坐在 3GB 并且开始遇到任何加载的问题。图像本身的范围从 600KB 到几兆字节。当它们被绘制时,它们会占用更多的空间。

    我找到了一个包装流实现 这里 (http://faithlife.codes/blog/2009/05/wrappingstream_implementation/) 由 Bradley Grainger 提供。

    这使我的内存使用率保持在相当低的水平;大多数时候小于 2GB。

    WrapperStream MasterStream{get;set;}
    
    private void ChangeImage()
    {
                SelectedImage = null;
                GC.Collect();
                var stream = new MemoryStream(ImageSource);
    
                using (MasterStream = new WrappingStream(stream))
                {
                    var bitmap = new BitmapImage();
                    bitmap.BeginInit();
                    bitmap.StreamSource = MasterStream;
                    bitmap.CacheOption = BitmapCacheOption.OnLoad;
                    bitmap.EndInit();
                    bitmap.Freeze();
                    SelectedImage = bitmap;
                }
    
                GC.Collect();
    
     }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-09-30
      • 1970-01-01
      • 1970-01-01
      • 2014-08-25
      • 1970-01-01
      • 2017-02-14
      • 1970-01-01
      相关资源
      最近更新 更多