dinghao

记录成长点滴

 

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引起问题的例子例子

[ViewModelExport(typeof(ChapterViewModel), "Chapter")]

[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,并且不易被发现。


var viewModelFactory = viewModelMapping.CreateExport();

viewModel 
= viewModelFactory.Value as IViewModel;

viewModelFactory.Dispose();

解决方式

通过其他方式注销事件,比如显式的接口、弱引用、匿名方法,在事件引起的内存泄露的文章中会详述几种解决方式。

在MVVM Light的cleanup注销事件


public override void Cleanup()

{

service.Loaded 
-= new EventHandler<AsyncEventArgs<object>>(service_Loaded);

Messenger.Default.Unregister(
this);

base.Cleanup();

}

MVVM Light的问题

按上面的方式本应该解决问题,但是service_Loaded 仍有可能不被执行。原来Mvvm Light为了保持和上一版本的兼容性在Dispose方法中调用了Cleanup。

重载Dispose


protected override void Dispose(bool disposing)

{

//,覆盖基类
禁止调用cleanup

}

结论

1. 尽量不要用Dispose释放托管资源,而使用自己的接口。

2. Dispose中绝对不应该调用其他方法,像MVVM light中调用cleanup,就会导致问题。

3.第三方框架引来的代码复杂性应该被考虑。

posted on 2011-04-12 16:57  思无邪  阅读(2658)  评论(3编辑  收藏  举报

导航