本文告诉大家一个已知问题,在保存图片元素对象时,如果在图片移除视觉树之后再设置图片源为空,那么原有的图片源依然被图片元素引用不会释放
如写一个按钮,在点击事件里面创建 RenderTargetBitmap 加入到新建的图片元素,然后在下次点击事件时,将图片元素从视觉树移除之后设置图片源为空
上面代码本身的 Image 元素就是内存泄漏的,因为 Image 元素被 Border 引用,加入到静态数组
但是 RenderTargetBitmap 也内存泄漏,虽然在图片移除视觉树之后设置 oldImage.Source = null;
也就是从代码上没有任何对象引用 RenderTargetBitmap 类,但是此类还是内存泄漏了
解决方法是在移除视觉树之前设置为空,同时调用 UpdateLayout 方法,或者在下一次 Dispatcher 将图片移除视觉树
我在 github 给微软报了这个问题,求点赞
Known issus: WPF Image memory leak when remove image from visual tree · Issue #2397 · dotnet/wpf
为什么会出现内存泄漏?原因是在图片继承的 UIElement 的布局方法会调用 OnRender 方法,而图片通过 DrawContext 的方式绘制了 Source 但是这个 DrawContext 的上下文被 UIElement 保存到 _drawingContent
字段
因为在调用 DrawContext 绘制图片时,将图片转换为MIL资源存放在 RenderData 类,而绘制完成之后将对应的值放在 _drawingContent
字段,也就是在 _drawingContent
引用了图片资源
此时设置图片的源为空,如果图片还在视觉树上,那么将会再次触发 OnRender 方法,在 OnRender 方法里面将会更新 RenderData 对图片源的引用
但是如果图片是被移除视觉树之后设置图片的源为空,那么不会再次触发 OnRender 方法,这样在 RenderData 存在对图片源的引用,此时将不会释放内存。如果在设置图片的源为空,然后不等待 OnRender 方法执行就将图片移除视觉树也是会内存泄漏。所以需要设置图片的源为空,然后调用 UpdateLayout 方法执行 OnRender 方法
其实这个内存泄漏问题很小,原因是如果 Image 元素对象没有被引用,那么图片就可以被释放,此时图片的源也可以释放
但是如果是一个大的做虚拟化的列表,此时在不可见的图片设置源为空,同时移除视觉树,此时图片的对象依然引用,虽然从代码上没有对图片源的引用,但是图片源依然在内存。也就是这个问题需要在做虚拟化列表时,注意对图片的移除视觉树
现在 WPF 开源了,有很多问题都可以从底层修改,欢迎大家关注WPF官方开源仓库 欢迎组队格式代码
其实我没有在本地编译成功 WPF 项目,所以干的最多的只是格式代码
原文链接: http://blog.lindexi.com/post/WPF-%E5%9B%BE%E7%89%87%E7%A7%BB%E9%99%A4%E8%A7%86%E8%A7%89%E6%A0%91%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。
欢迎转载、使用、重新发布,但务必保留文章署名 林德熙 (包含链接: https://blog.lindexi.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我 联系。