本文为个人博客备份文章,原文地址:

http://validvoid.net/win2d-avoiding-memory-leaks/

在托管 XAML 应用中使用 Win2D 控件时,必须谨慎处理对象引用计数,以免控件不能被垃圾回收器回收。

内存泄漏的发生条件

  • 你正在通过 C# 等 .Net 语言 (非原生 C++)使用 Win2D
  • 你使用了以下任一 Win2D 控件:
    • CanvasControl
    • CanvasVirtualControl
    • CanvasAnimatedControl
    • CanvasSwapChainPanel
  • 你订阅了 Win2D 控件的事件(如 DrawCreateResourcesSizeChanged...)
  • 你的应用在多个 XAML 页面中前进后退进行导航

如果以上情况全部满足,那么一个引用计数循环会阻止垃圾回收器回收 Win2D 控件。 在应用每次导航到一个新页面时,新的 Win2D 资源都会进行分配,而老资源则一直未被释放,从而造成内存泄漏。要避免这一情况,你必须手动添加代码打破引用计数循环。

如何修复

要打破引用计数循环以便使也页面能够被垃圾回收,你需要:

  • 订阅包含 Win2D 控件的 XAML 页面的 Unloaded 事件。
  • 在 Unloaded 事件的处理逻辑中,调用 Win2D 控件的 RemoveFromVisualTree 方法。
  • 在 Unloaded 事件的处理逻辑中,(通过将控件实例设为 null)释放任何对 Win2D 控件的直接引用。

示例代码:

void page_Unloaded(object sender, RoutedEventArgs e)
{
this.canvas.RemoveFromVisualTree();
this.canvas = null;
}

完整示例可以参见示例项目中的 demo 页面。

测试循环泄露

要测试你的应用是否正确打破了引用计数循环,可以在包含 Win2D 控件的页面里添加析构函数:

~MyPage()
{
System.Diagnostics.Debug.WriteLine("~" + GetType().Name);
}

在应用的 App 构造函数中添加一个定时器以使垃圾回收定期执行:

var gcTimer = new DispatcherTimer();
gcTimer.Tick += (sender, e) => { GC.Collect(); };
gcTimer.Interval = TimeSpan.FromSeconds();
gcTimer.Start();

之后可启动应用进行调试,导航到页面,再导航到其它页面。如果全部引用循环被正确打破,一两秒内你就能在 Visual Studio 的输出面板看到 Debug.WriteLine 输出的信息。

注意

调用 GC.Collect 方法具有破坏性,会降低性能。因此一旦你完成内存泄漏测试,请务必移除此测试代码。

关键细节

当对象 A 引用了对象 B,同时对象 B 也引用了对象 A时,一个引用循环就会发生。当对象 A 引用对象 B, 对象B 引用对象 C,而对象C 引用对象A 等情况亦然。

当订阅一个 XAML 控件的事件时,这种引用循环不可避免:

  • XAML 页面承载了全部其所包含控件的引用
  • 控件承载了所有订阅其事件委托的引用
  • 每个委托承载了其目标实例的引用
  • 每个事件处理程序都是 XAML 页面类的实例方法,所以他们的目标实例又指向回 XAML 页面从而形成一个引用循环

如果以上引用过程中的所有对象都是 .Net 实现的,则引用循环并不会成问题。因为 .Net 能够自动进行垃圾回收,即便对象形成了引用循环,垃圾回收算法也能够识别并回收它们。

不同于 .Net, C++ 通过引用计数管理内存,它不能自动探测并回收对象引用循环。尽管有此限制, C++ 应用使用 Win2D 是没有问题的。因为 C++ 的事件处理程序默认承载对象实例的弱引用而非强引用。 因此页面引用控件,控件引用事件处理委托,而委托并没有引用回页面,因此不会产生循环。

而当一个 .Net 应用使用 Win2D 这类 C++ WinRT 组件时问题就出现了:

  • XAML 页面是应用的一部分,因此使用垃圾回收机制
  • Win2D 控件是由 C++ 实现的,因此使用引用计数机制
  • 事件处理委托是应用的一部分,因此使用垃圾回收机制并承载对目标示例的强引用

一个引用循环由此产生,而没有使用 .Net 垃圾回收机制的 Win2D 对象也参与了其中。 这意味着垃圾回收器无法查看整个引用链,因而它也无法探测和回收对象。当这一情况发生时,则必须通过显式打破引用循环来解决问题。一个方法是释放所有页面对控件的引用(如上文推荐那样);另外也可以找到所有可能指向回页面的事件处理委托,释放掉控件对这些委托的引用(在页面的 Unloaded 事件中解除所有事件处理的订阅)。

Win2D 官方文章系列翻译 - 避免内存泄漏的更多相关文章

  1. Win2D 官方文章系列翻译 - 处理设备丢失

    本文为个人博客备份文章,原文地址: http://validvoid.net/win2d-handling-device-lost/ “设备丢失”是指 GPU 设备失效无法继续进行渲染的情况.GPU ...

  2. Win2D 官方文章系列翻译 - DPI (每英寸点数)和 DIPs(设备独立像素)

    本文为个人博客备份文章,原文地址: http://validvoid.net/win2d-dpi-dips/ 本文旨在解释物理像素与设备独立像素(DIPs, device independent pi ...

  3. Win2D 官方文章系列翻译 - 幕后绘制

    本文为个人博客备份文章,原文地址: http://validvoid.net/win2d-offscreen-drawing/ 应用有时需要将图形绘制到并不立即显示的目标上.此类绘制动作被称作“幕后绘 ...

  4. Win2D 官方文章系列翻译 - 预乘 Alpha

    本文为个人博客备份文章,原文地址: http://validvoid.net/win2d-premultiplied-alpha/ 在计算机绘图中有两种表示颜色值不透明度的方法.Win2D 中两种方法 ...

  5. Win2D 官方文章系列翻译 - 像素格式

    本文为个人博客备份文章,原文地址: http://validvoid.net/win2d-pixel-formats/ DirectXPixelFormat 枚举 包含了 Direct3D 和 DXG ...

  6. Win2D 官方文章系列翻译 - 与 Direct2D 互操作

    本文为个人博客备份文章,原文地址: http://validvoid.net/win2d-interop-with-direct2d/ Win2D 作为 Direct2D 的上层实现,支持与其进行双向 ...

  7. Win2D 官方文章系列翻译 - 调整控件分辨率

    本文为个人博客备份文章,原文地址: http://validvoid.net/win2d-choosing-control-resolution/ 本文旨在讲解如何配置 Win2D XAML 控件使用 ...

  8. Win2D 入门教程 VB 中文版 - 防止内存泄漏

    避免内存泄漏 本文从微软官方文档翻译 http://microsoft.github.io/Win2D/html/RefCycles.htm 如果文档有问题,可以在 https://github.co ...

  9. 18.一篇文章,从源码深入详解ThreadLocal内存泄漏问题

    1. 造成内存泄漏的原因? threadLocal是为了解决对象不能被多线程共享访问的问题,通过threadLocal.set方法将对象实例保存在每个线程自己所拥有的threadLocalMap中,这 ...

随机推荐

  1. lucene、lucene.NET详细使用与优化详解

    lucene.lucene.NET详细使用与优化详解 2010-02-01 13:51:11 分类: Linux 1 lucene简介1.1 什么是luceneLucene是一个全文搜索框架,而不是应 ...

  2. 【mysql】SQL常用指令

    常用操作指令 show databases;显示所有的数据库: use dbName; 使用指定数据库 show tables; 显示所有的数据表: desc tableName; 查看数据表的字段信 ...

  3. 样本方差:为嘛分母是n-1

    在样本方差计算式中,我们使用Xbar代替随机变量均值μ. 容易证明(参考随便一本会讲述样本方差的教材),只要Xbar不等于μ,sigma(Xi-Xbar)2必定小于sigma(Xi-μ)2. 然而,要 ...

  4. Python-While刷博爬虫

    仅用于测试 #!/usr/bin/python import webbrowser as web import time import os url = 'www.abc.com' while Tru ...

  5. CentOS7安装Oracle 11g R2 详细过程——零基础

    本人linux小白,因项目原因必须要在linux下使用oracle便开始了探索.安装过程中遇到了种种问题与原因,今天整理一下方便后面的可以少走弯路. *注明: 安装过程注意当前错作的用户,执行./ru ...

  6. ACL权限设置使用

    acl操作 # 查看操作的分区支不支持acldumpe2fs -h /dev/sda2 | grep aclDefault mount options: user xattr acl 代表支持acl如 ...

  7. DISPOSE_ON_CLOSE 和 EXIT_ON_CLOSE 的区别

    If you have several JFrames open and you close one that has EXIT_ON_CLOSE it will close all the JFra ...

  8. json字符串相关转换方法

    /** json转换为Map * @param jsonStr json * @return map集合 */ public static HashMap<String, String> ...

  9. MongoDB的基本使用

    use library 使用use函数切换已有的数据库或创建新的数据库 show dbs 查看MongoDB中目前所有可用的数据库 show collections 查看当前数据库中的所有集合 在集合 ...

  10. 2.Could not open Selected VM debug port (8700). Make sure you do not have another instance of DDMS or of the eclipse plugin running

    在eclipse.ini配置文件最后加上:-Djava.net.preferIPv4Stack=true 其他类似