Silverlight内存泄露(二)解决内存泄露之Dispose误用
按顺序第二篇应该是”如何检测SIlverlight内存泄露”,但这一系列都是解决实际问题的日志,有些检测结果没有被保存下来,不可能为了写这些文章,而重现bug。想到哪就写到哪了。
看到许多文章在解决事件为被注销引起内存泄露时,通过在Dispose中注销事件,以释放资源,这可能会出现未预料的问题。
Dispose模式介绍
参考:Effective C# - 条款18:实现标准的Dispose模式
要点:
通过实现IDisposable接口,你写成了两件事:第一就是提供了一个机制来及时的释放所有占用的托管资源,另一个就是你提供了一个标准的模式让用户来释放非托管资源。这是十分重要的,当你在你的类型上实现了IDisposable接口以后,用户就可以避免析构时的损失。你的类就成了.Net社 区中表现相当良好的成员。
但在你创建的机制中还是存在一些漏洞。如何让一个派生类清理自己的资源,同时还可以让基类很好的再做资源清理呢?(译注:因为调用Dispose方法时, 必须调用基类的Dispose,当然是在基类有这个方法时。但前面说过,我们只有一个标记来标识对象是否处理过,不管先调用那个,总得有一个方法不能处理这个标记,而这就存在隐患) 如果基类重载了析构函数,或者自己添加实现了IDisposable接口,而这些方法又都是必须调用基类的方法的;否则,基类无法恰当的释放资源。同样,析构和处理共享了一些相同的职责:几乎可以肯定你是复制了析构方法和处理方法之间的代码。正如你会在原则26中学到的,重载接口的方法根本没有如你所期望的那样工作。Dispose标准模式中的第三个方法,通过一个受保护的辅助性虚函数,制造出它们的常规任务并且挂接到派生类来释放资源。基类包含接口的核心代码, 派生类提供的Dispose()虚函数或者析构函数来负责清理资源
Dispose可能存在的问题
Dispose是Object就有的方法,任何代码都有可能调用,并且子类应该调用基类的Dispose。从中可看出:
1. Dispose是.Net固有的方法,任何代码都可能调用
2. Dispose是自上而下执行的,基类到子类。
这表明Dispose是不可控的,其他代码很可能会调用这个方法。
MVVM light最初也依赖Dispose释放资源,但在新版本中已经使用ICleanUp接口释放资源,就是由于Dispose太不可控了。但为了兼容前版本,Dispose仍然有效。
Dispose引起问题的例子例子
[PartCreationPolicy(CreationPolicy.NonShared)]
public class ChapterViewModel:NavViewModel
{
ChapterService service = new ChapterService();
public C
[ViewModelExport(typeof(ChapterViewModel), "Chapter")]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class ChapterViewModel:NavViewModel
{
ChapterService service = new ChapterService();
public ChapterViewModel()
{
service.Loaded += new EventHandler<AsyncEventArgs<object>>(service_Loaded);
}
void service_Loaded(object sender, AsyncEventArgs<object> arg)
{
}
public void Dispose()
{
service.Loaded -= new EventHandler<AsyncEventArgs<object>>(service_Loaded);
}
}
hapterViewModel()
{
service.Loaded += new EventHandler<AsyncEventArgs<object>>(service_Loaded);
}
void service_Loaded(object sender, AsyncEventArgs<object> arg)
{
}
public void Dispose()
{
service.Loaded -= new EventHandler<AsyncEventArgs<object>>(service_Loaded);
}
}
按照预想Dispose会注销事件,避免内存泄露,但运行时会发现service_Loaded可能不被执行,原因是 Dispose()被第三方程序调用了,而这些调用可能很隐蔽。
在这个程序里,下面的代码调用了Dispose,并且不易被发现。
viewModel = viewModelFactory.Value as IViewModel;
viewModelFactory.Dispose();
解决方式
通过其他方式注销事件,比如显式的接口、弱引用、匿名方法,在事件引起的内存泄露的文章中会详述几种解决方式。
在MVVM Light的cleanup注销事件
{
service.Loaded -= new EventHandler<AsyncEventArgs<object>>(service_Loaded);
Messenger.Default.Unregister(this);
base.Cleanup();
}
MVVM Light的问题
按上面的方式本应该解决问题,但是service_Loaded 仍有可能不被执行。原来Mvvm Light为了保持和上一版本的兼容性在Dispose方法中调用了Cleanup。
重载Dispose
{
//,覆盖基类
禁止调用cleanup
}
结论
1. 尽量不要用Dispose释放托管资源,而使用自己的接口。
2. Dispose中绝对不应该调用其他方法,像MVVM light中调用cleanup,就会导致问题。
3.第三方框架引来的代码复杂性应该被考虑。