在 WPF 中,使用 Stroke 类时,可能会出现内存泄露,原因是 DrawingAttributes 的事件被监听没有释放。本文将从源代码的角度告诉大家这个内存泄露问题和如何解决
在满足如下条件的时候,将会让 Stroke 类出现内存泄露
- 存在一个 Stroke 被强引用,将这个 Stroke 记为 A 对象
- 取 A 对象的 DrawingAttributes 属性,创建出另一个新的 Stroke 对象,将这个对象记为 B 对象
此时将会发现 B 对象不会被释放,如 demo 所示,点击按钮可以看到内存不会释放
实现上面条件的代码很简单,请看代码
代码语言:javascript复制 var stroke = new Stroke(new StylusPointCollection(stylusPointList), StrokeVisual.Stroke.DrawingAttributes);
上面代码的 StrokeVisual 是一个窗口的属性,也就是说 StrokeVisual.Stroke 是存在强引用。此时创建出来的 stroke 对象将不会被回收
按钮点击的代码如下
代码语言:javascript复制 public StrokeVisual StrokeVisual { get; set; }
private void Button_OnClick(object sender, RoutedEventArgs e)
{
for (int i = 0; i < 100; i )
{
var stylusPointList = new List<StylusPoint>();
for (int j = 0; j < 1000; j )
{
stylusPointList.Add(new StylusPoint(i, i));
}
var stroke = new Stroke(new StylusPointCollection(stylusPointList), StrokeVisual.Stroke.DrawingAttributes);
}
}
关于 StrokeVisual 的定义,请看 WPF 最简逻辑实现多指顺滑的笔迹书写
那为什么使用一个被强引用的 Stroke 的 DrawingAttributes 去创建另一个 Stroke 对象,会让另一个 Stroke 不会被释放
通过 WPF 的源代码可以看到,在 Stroke 里面是将 DrawingAttributes 作为属性存放,因此 Stroke 强引用 DrawingAttributes 对象。如果使用被强引用的 Stroke 的 DrawingAttributes 去创建另一个 Stroke 对象,因为在 Stroke 对象的构造函数里面有如下代码
代码语言:javascript复制internal Stroke(StylusPointCollection stylusPoints, DrawingAttributes drawingAttributes, ExtendedPropertyCollection extendedProperties)
{
_drawingAttributes = drawingAttributes;
_drawingAttributes.AttributeChanged = new PropertyDataChangedEventHandler(DrawingAttributes_Changed);
}
也就说在 Stroke 里面添加了 _drawingAttributes
的事件,也就说 Stroke 也被 DrawingAttributes 强引用。而如上面描述,这里的 DrawingAttributes 被另一个 Stroke 强引用。因此如果多个 Stroke 使用相同的一个 DrawingAttributes 对象,有一个 Stroke 被强引用,那么其他的所有的 Stroke 对象也不会被释放
本文的测试代码放在 github 和 gitee 欢迎下来进行调试
那如何解决此问题?在 DrawingAttributes 对象里面提供了 Clone 方法,在使用某个 Stroke 的 DrawingAttributes 对象创建一个新的 Stroke 的时候,如果要解决本文提到的的坑,可以调用 DrawingAttributes 的 Clone 方法创建一个新的 DrawingAttributes 对象,此时这个新的 DrawingAttributes 对象将不会被原有的 Stroke 强引用,因此也就不会让新创建的 Stroke 因为被 DrawingAttributes 强引用的原因内存泄露
代码语言:javascript复制 var stroke = new Stroke(new StylusPointCollection(stylusPointList), StrokeVisual.Stroke.DrawingAttributes.Clone());
如上面代码,通过在新建 Stroke 的时候,传入的 DrawingAttributes 是调用 Clone 方法创建的
这个问题报告给了 WPF 官方,请看 WPF Stroke may memory leak
当然了,这个也不算是坑,通过 VisualStudio 进行内存调试,是可以找到这个坑的
其实通过阅读源代码,如果给多个 Stroke 对象使用相同的一个 StylusPointCollection 对象,那么也有相同的坑。此时只要有一个 Stroke 被强引用了,那么所有的 Stroke 都不会释放
代码语言:javascript复制internal Stroke(StylusPointCollection stylusPoints, DrawingAttributes drawingAttributes, ExtendedPropertyCollection extendedProperties)
{
_drawingAttributes = drawingAttributes;
_drawingAttributes.AttributeChanged = new PropertyDataChangedEventHandler(DrawingAttributes_Changed);
_stylusPoints = stylusPoints;
_stylusPoints.Changed = new EventHandler(StylusPoints_Changed);
_stylusPoints.CountGoingToZero = new CancelEventHandler(StylusPoints_CountGoingToZero);
}
当前的 WPF 在 https://github.com/dotnet/wpf 完全开源,使用友好的 MIT 协议,意味着允许任何人任何组织和企业任意处置,包括使用,复制,修改,合并,发表,分发,再授权,或者销售。在仓库里面包含了完全的构建逻辑,只需要本地的网络足够好(因为需要下载一堆构建工具),即可进行本地构建
更多笔迹相关请看
- WPF 渲染原理
- 高性能笔迹原理
- WPF 高性能笔
- WPF 高速书写 StylusPlugIn 原理
- WPF 最小的代码使用 DynamicRenderer 书写
- WPF 使用 Composition API 做高性能渲染
- WPF 使用 Win2d 渲染
- win10 uwp win2d CanvasVirtualControl 与 CanvasAnimatedControl
- WPF 最简逻辑实现多指顺滑的笔迹书写
本文会经常更新,请阅读原文: https://blog.lindexi.com/post/dotnet-读-WPF-源代码笔记-Stroke-类可能存在的内存泄露.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。
如果你想持续阅读我的最新博客,请点击 RSS 订阅,推荐使用RSS Stalker订阅博客,或者前往 CSDN 关注我的主页
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名林德熙(包含链接: https://blog.lindexi.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 。
无盈利,不卖课,做纯粹的技术博客