异步加载BitmapSource图像时发生内存泄漏。

4

我在我的WPF应用程序中将许多图像加载到ListBox中。最初,我使用GDI来调整图像的大小(原始图像占用太多内存)。这很好,但每个图像需要大约400毫秒。这不太好。因此,我寻找另一种解决方案,我找到了一种使用TransformedBitmap(继承自BitmapSource)的方法。我认为这很棒,我可以使用它。但现在我遇到了某些内存泄漏问题...

我正在使用BackgroundWorker异步加载图像,如下所示:

BitmapSource bs = ImageUtils.ResizeBitmapSource(ImageUtils.GetImageSource(photo.FullName));
                //BitmapSource bs = ImageUtils.GetImageSource(photo.FullName);
                bs.Freeze();

                this.dispatcher.Invoke(new Action(() => { photo.Source = bs; }));

GetImageSource函数从路径获取位图,然后转换为BitmapSource。

以下是ResizeBitmapSource的代码片段:

const int thumbnailSize = 200;
        int width;
        int height;

        if (bs.Width > bs.Height)
        {
            width = thumbnailSize;
            height = (int)(bs.Height * thumbnailSize / bs.Width);
        }
        else
        {
            height = thumbnailSize;
            width = (int)(bs.Width * thumbnailSize / bs.Height);
        }

        BitmapSource tbBitmap = new TransformedBitmap(bs,
                                                           new ScaleTransform(width / bs.Width,
                                                                              height / bs.Height, 0, 0));

        return tbBitmap;

这段代码本质上是从以下链接中获取的: http://rongchaua.net/blog/c-wpf-fast-image-resize/

有什么想法可能导致泄漏吗?

编辑: 如所请求,以下是GetImageSource的代码:

using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read))
            {
                using (var bmp = Image.FromStream(stream, false, false))
                {
                    // Use WPF to resize
                    var bitmapSource = ConvertBitmapToBitmapSource(bmp);
                    bitmapSource = ResizeBitmapSource(bitmapSource);
                    return bitmapSource;
                }
            }

你是如何诊断这个漏洞的?为什么你认为它在这段代码中而不是例如GetImageSource()中? - H H
我在WPF控件中使用的BitmapSource已经被正确地调整大小,但是查看进程使用的内存时,它使用的内存与完整大小的图像相同。因此,在某个地方,完整大小的BitmapSource没有被删除。编辑:因为GetImageSource实际上并没有改变。如果我选择使用GDI进行调整大小,那么就没问题。但是当我使用这种TransformedBitmap方法进行调整大小时,它不起作用。 - Harry
1
你能尝试手动调用垃圾回收器,看看内存是否被释放吗?如果没有,很可能是某个地方有一个引用在持有内存。 - Mikael Svenson
你能否请发一下 ImageUtils.GetImageSource() 的代码?我怀疑内存泄漏就是在那里发生的。 - Alastair Pitts
我已经将代码添加到GetImageSource中。ConvertBitmapToBitmapSource()的代码只是使用Imaging.CreateBitmapSourceFromHBBitmap()。我认为它可能是因为这个方法,但不仅我在原始代码中使用了这个方法,而且我还删除了Hbitmap IntPtr... - Harry
请查看https://dev59.com/EHM_5IYBdhLWcg3whznx,以获取手动GC和修剪工作集的代码,以查看是否会释放内存。 - Mikael Svenson
2个回答

4
我认为你误解了TransformedBitmap的工作原理。它持有对源位图的引用,并在内存中进行变换。也许您可以将转换后的位图编码成内存流,然后立即读取。我不确定这样做的速度有多快,但您就不会再持有完整大小的位图了。
我发现了这篇博客文章,它返回一个以TransformedBitmap为源的WriteableBitmap。WriteableBitmap会在初始化器中将像素数据复制到内存缓冲区中,因此它实际上没有保留对TransformedBitmap或完整大小的图像的引用。

好的,我明白你的意思了。明天早上我会尝试一下,谢谢。 - Harry

1

从你的代码来看,我猜测你可能需要处理通过调用ImageUtils.GetImageSource(photo.FullName)返回的位图。

我还注意到你指出的博客中作者已经添加了一个更新(3月11日),关于插入using语句以防止内存泄漏。


是的,那是基于我添加的评论。但它似乎并没有起作用。并不是说他的代码有问题,而是我的代码里有些东西让它出现了偏差。 - Harry

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接