发布日期:
2009-02-24 17:28 发布者: cobra
原版编程图书超低价热卖
1#
t
T
背景这两天帮助其它 项目组Review 代码, 发现有些地方实现了IDispose 接口, 同时也发现了一些关于IDispose 的问题: 1.A 类型实现了IDispose 接口,B 类型里面含有A 类型的字段,B 类型没有实现IDispose 接口2. 一个类里面实现了Finalize 终结器, 同时也实现了IDispose 接口, 但在Dispose 方法里面没有调用GC.SuppressFinalize(this) 方法. 下面我对以上两个问题分别分析一下, 并提出解决方案. 问题1: 如果A 类型里面有非托管资源需要在实现的IDispose 接口里面释放, 由于B 类型没有实现IDispose 接口,B 类型的使用者要想释放A 类型的非托管资源并不方便. 这样的话, 就有可能忘记了释放A 类型的非托管资源. 解决方案: 实现B 类型的IDispose 接口, 在Dispose 方法里面调用A 类型的Dispose 方法. 这样,B 类型的使用者在调用B 类型Dispose 的同时, 就把A 类型的Dispose 也调用了. 问题2: 在Dispose 方法里面没有调用GC.SuppressFinalize(this) 方法, 会有什么问题呢, 这样会导致垃圾回收器不能对这个类型的对象及时回收. 当GC 开始工作的时候,它首先将没有终结器的垃圾对象从内存中移除,有终结器的所有对象则添加到一个垃圾队列当中。GC 会调用一个新线程来执行这些对象的终结器。当终结器执行完毕后,这个对象会从队列中被移除。这个对象在队列中移除之后, 当GC 再次开始工作的时候, 这个对象才能够被回收, 所以有终结器的对象会比没有的在内存中保留更长的时间。在后面我会对这里再详细的描述一下. 解决方案: 在Dispose 方法中调用GC.SuppressFinalize(this) 方法. 这样的话, 就不会把有终结器的对象则添加到垃圾队列当中. 切入正题.net 中, 非托管代码清理有两种方式:Finalize 方式和Dispose 方式. Finalize方式: 通过对自定义类型实现一个Finalize 方法来释放非通过资源. 从.net2.0 开始,C# 编译器不能对Finalize 进行显示的调用和重写, 必须使用析构函数来实现它. - class A
- {
- ~A()
- {
- 释放资源;
- }
- }
复制代码 上面的代码就是通过Finalize 方式来释放资源的跟C++ 用析构函数释放资源的代码很象. 但是它实现方式和C++ 不同, 因为它是由垃圾回收器来管理内存的. 大家看到了, 用Finalize 方式释放非托管资源很简单, 但是如果你了解了他的实现方式, 你可能就不会选择用它来释放非托管资源. 那Finalize 方式在.net 内部是如何实现的呢? 当GC( 垃圾回收器) 开始工作的时候,它首先将没有终结器的垃圾对象从内存中移除,有终结器的所有对象则添加到一个终止化队列当中。GC 会调用一个新线程来执行这些对象的终结器。当终结器执行完毕后,这些对象会从队列中被移除。这时候由于这些对象在第一次检测到的时候没有被释放, 它们将会进入第1 代对象, 直到GC 检测到第0 代对象和第1 代对象再次充满时, 这时候GC 才会把刚才那些对象释放掉, 所以有终结器的对象会比没有的在内存中保留更长的时间。提示: 垃圾回收器把托管堆中的对象分为3 代, 分别是0,1,2. 一般分配为:0 代约256K,1 代约是2MB, 第2 代约是MB, 代龄越高, 容量就越大, 显然效率也就越低. 首先被添加到托管堆中的对象被定为第0 代, 当第0 代充满时, 就会执行垃圾回收, 未被回收的对象代领将提升1 代. 由于以上原因应该避免仅使用Finalize 方式释放非托管资源. Dispose模式: 在自定义类中实现IDispose 接口, 在接口中的Dispose 方法中对非托管资源进行释放. 闲话少说, 上代码- public class MyResourceRelease: IDisposable
- {
- /// <summary>
- /// 保证资源只用释放一次
- /// </summary>
- private bool _alreadyDisposed = false;
- /// <summary>
- ///
- /// </summary>
- /// <param name="isDisposing">用来判断释放资源的类别(托管和非托管)</param>
- protected virtual void Dispose(bool isDisposing)
- {
- if(_alreadyDisposed)
- {
- return;
- }
- if(isDisposing)
- {
- //释放托管资源
- }
- //释放非托管资源
- _alreadyDisposed = true;
- }
- public void Dispose()
- {
- Dispose(true);
- }
- }
复制代码 上面的代码就是用Dispose 方式释放资源的方法. 因为上面自定义的Dispose(bool isDisposing) 方法是virtual 的, 所以还可以在派生类里面对它进行override - public class MyDerivedResource: MyResourceRelease
- {
- private bool _disposed = false;
- protected override void Dispose(bool isDisposing)
- {
- if(_disposed)
- {
- return;
- }
- try
- {
- if(isDisposing)
- {
- //释放托管资源
- }
- //释放非托管资源
- _disposed = true;
- }
- finally
- {
- base.Dispose(isDisposing);
- }
- }
- }
复制代码 这样可以确保释放继承链上所有对象的引用资源, 在整个继承层次中传播Dispose 模式. 思考那用Dispose 方式非托管资源就是最好的方法了吗? 其实不然, 因为类型实现了IDispose 接口, 这个类的使用者必须显示调用Dispose 方法, 或者在创建该类型对象的时候使用using 关键字, 对于一些粗心的使用者可能会忘记调用Dispose 方法, 或者没有使用using 关键字, 这样就导致了非托管资源没有释放的后果. 最佳方案同时实现终结器和Dispose 方式. 这样对于细心的使用者直接显示调用Dispose 方法会提高垃圾回收的性能, 对于粗心的使用者虽然忘记了调用Dispose 方法, 但也不至于使得非托管资源得不到释放. 注意这里用到了GC. SuppressFinalize(this) 方法. 代码如下: - public class MyResourceRelease: IDisposable
- {
- ~MyResourceRelease()
- {
- Dispose(false);
- }
- /// <summary>
- /// 保证资源只用释放一次
- /// </summary>
- private bool _alreadyDisposed = false;
- /// <summary>
- ///
- /// </summary>
- /// <param name="isDisposing">用来判断释放资源的类别(托管和非托管)</param>
- protected virtual void Dispose(bool isDisposing)
- {
- if(_alreadyDisposed)
- {
- return;
- }
- if(isDisposing)
- {
- //释放托管资源
- }
- //释放非托管资源
- _alreadyDisposed = true;
- }
- public void Dispose()
- {
- Dispose(true);
- //阻止GC把该对象放入终结器队列
- GC.SuppressFinalize(this);
- }
- }
复制代码文/ 我的地盘我做主 出处/博客园
|