专栏首页林德熙的博客WPF 图片移除视觉树内存泄漏

WPF 图片移除视觉树内存泄漏

本文告诉大家一个已知问题,在保存图片元素对象时,如果在图片移除视觉树之后再设置图片源为空,那么原有的图片源依然被图片元素引用不会释放

如写一个按钮,在点击事件里面创建 RenderTargetBitmap 加入到新建的图片元素,然后在下次点击事件时,将图片元素从视觉树移除之后设置图片源为空

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            // 每次点击此按钮会将当前呈现的图片移除视觉树,再将其Source属性设置为null。
            // 然后新建一个Image控件,并将其Source属性设置为RenderTargetBitmap对象,再呈现出来。
            // 再次过程中,RenderTargetBitmap对象从来不会被回收,造成内存泄露。
            // 可以从资源管理其中观察到程序的内存持续上涨的现象。

            // Remove the current Image control from the  visual tree and set source is null when click button.
            // Then new a image control and add source to the RenderTargetBitmap object and show it.
            // You can see the gc never delete the RenderTargetBitmap object that make  memory leak.


            var oldBorder = RootGrid.Children.OfType<Border>().LastOrDefault();
            if (oldBorder != null)
            {
                var oldImage = (Image)oldBorder.Child;


                // 如果在Image控件移除视觉树之前将其Source属性设为null,并调用UpdateLayout方法。
                // 则RenderTargetBitmap对象可被回收,不会导致内存泄露。
                // 取消注释下面的代码可以观察到上述现象。
                // In order to solve it , you should set the image.Source is null and use UpdateLayout.
                // The below code can solve it.
                // oldImage.Source = null;
                // oldImage.UpdateLayout();

                // 将当前的Image控件移除视觉树。
                // Remove the current Image control from the  visual tree.
                RootGrid.Children.Remove(oldBorder);
                oldImage.Source = null;
                Borders.Add(oldBorder);
            }

            var bitmap = new RenderTargetBitmap(1024, 1024, 96, 96, PixelFormats.Default);

            var image = new Image { Source = bitmap };
            var border = new Border { Child = image };
            RootGrid.Children.Add(border);

            // 为了便于观察内存的变化,每次操作后都会进行垃圾回收。
            // In order to facilitate changes in memory, after each operation will be garbage collection
            GC.Collect();
        }

        public static readonly List<Border> Borders = new List<Border>();

上面代码本身的 Image 元素就是内存泄漏的,因为 Image 元素被 Border 引用,加入到静态数组

但是 RenderTargetBitmap 也内存泄漏,虽然在图片移除视觉树之后设置 oldImage.Source = null; 也就是从代码上没有任何对象引用 RenderTargetBitmap 类,但是此类还是内存泄漏了

解决方法是在移除视觉树之前设置为空,同时调用 UpdateLayout 方法,或者在下一次 Dispatcher 将图片移除视觉树

     oldImage.Source = null;
     oldImage.UpdateLayout();

我在 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 项目,所以干的最多的只是格式代码

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • docfx 做一个和微软一样的文档平台 下载安装创建文档文件生成文档查看文档添加文档添加代码文档做自己的修改忽略不使用的api继续在微软上开发

    开发中,有一句话叫 最不喜欢的是写文档,最不喜欢的是看别人家代码没有文档。那么世界上文档写最 la 好 ji 的就是微软了,那么微软的api文档是如何做的?难道...

    林德熙
  • WPF 控件继承树

    本文会经常更新,请阅读原文: https://lindexi.gitee.io/post/WPF-%E6%8E%A7%E4%BB%B6%E7%...

    林德熙
  • dotnet Microsoft.Recognizers.Text 超强大的自然语言关键词提取库

    本文和大家介绍一个使用超级简单,但是功能特别强大的自然语言关键词提取库,可以根据输入的自然语言提取出里面的信息。例如我在一句话里面说了哪些数值变量或者说了手机号...

    林德熙
  • 响铃:AI+生活场景渗透,个人网盘的再生新通道?

    2016年可谓是网盘行业的“生死之年”。从2013年开始群雄逐鹿疯狂抢占市场份额的势头不再,迅雷快盘、华为网盘、新浪微盘、UC网盘、360个人云盘、乐视个人云盘...

    曾响铃
  • 悟空活动中台 - 基于 WebP 的图片高性能加载方案

    移动端网页的加载速度对用户体验极为重要,是影响页面转化率的关键因素,H5 活动页往往使用大量的图片素材来丰富活动效果,素材加载的快慢会对用户感知造成重要的影响。

    2020labs小助手
  • iOS 中的 Promise 设计模式

    无论是代理模式,还是闭包,在处理单一任务的时候,都出色的完成了任务。可是当两种模式要相互配合,一起完成一系列任务,并且每个任务之间还要共享信息,相互衔接,雇主就...

    QQ音乐技术团队
  • Flutter组件学习(二)—— Image

    上一节中,我们讲了 Flutter 中 Text 组件的一些用法以及 API,本节我们继续学习 Flutter 中的 Image 组件,同样先上图:

    用户2802329
  • centos服务器安装code-server

    使用screen后台运行,未安装时输入yum -y install screen进行安装

    薛定喵君
  • 设计模式之备忘录模式(Memento模式)引入备忘录模式备忘录模式的实例备忘录模式分析

    我们在使用文本编辑器的时候,一般如果不小心误操作了,按ctrl+z就可以恢复之前的状态,撤销(undo)操作。 撤销的操作,实际上有两步,一是要保存之前的状态...

    desperate633
  • Ubuntu18安装docker-ce 原

    2.新建并编辑etc/apt/sources.list.d/docker.list文件

    bdcn

扫码关注云+社区

领取腾讯云代金券