首页 > 代码库 > 标准IDispose模式浅析
标准IDispose模式浅析
DoNet资源
众所周知,.Net内存管理分托管资源和非托管资源,把内存中的对象按照这两种资源划分,然后由GC负责回收托管资源(Managed Resource),而对于非托管资源来讲,就需要程序员手动释放。
Framework的设计者的本意是降低Developer的入门难度,提高开发效率,让使用者更少的关注“垃圾回收”,但也正是如此的封装,才导致越来越多的滥用,甚至可怕的效率低下。(这里,我本人强调的是对DoNet垃圾回收机制的理解程度导致的滥用现象。)常常有人抱怨资源一直没有被回收,或者没有按照时间点回收,或者创建再生资源时,效率低下。(“再生资源”指刚刚被完全释放的资源,依据《.Net设计规范》中的说法,不适当的回收导致在创建时的效率低下。)
说了这些就是想提醒大家,我们要有一个更正确,更完善的方式回收资源。
IDisposable接口
在Framework的设计中很多地方都实现了IDisposable接口的Dispose方法,GC会自动地,随机地调用某个资源的析构方法,从而达到自动回收垃圾的目的,该接口来自于System命名空间下。
所以,对于我们自定义的类来讲一般都要实现IDisposable接口,已完成自动回收。
标准IDispose模式
不罗嗦了,网上相关的资料很多,这里写下来也是让自己更熟悉这种模式,毕竟理解是一回事,讲解出来又是另一回事。
首先引用IDisposable接口,然后,没有然后了,直接贴代码:
#region 标准IDispose模式 Private bool disposed = false; Public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } ~XXXXClass() { Dispose(false); } Protected virtual void Dispose(bool disposing) { If(disposed) Return; If(disposing) { If(Resource != null) { // Release managed resource } } // Release unmanaged resource disposed = true; } #endregion
- GC.SuppressFinalize(this); 这个方法是告诉GC一个对象已经手动释放过,并且不需要再释放了,这样的话,对象能被更早的再生。一般是紧跟在Dispose(true)之后。
- ~XXXXClass()析构函数,函数释放时自动执行。其中Dispose(false)程序自动调用时,只回收非托管资源。
- Protected virtual void Dispose(bool disposing) 标识为 Protect 是为了更好的封装,virtual是便于继承类重写。
- 对于某些资源的释放要做如下三部:
a) A != null如果为空的话,不用释放。
b) A.Dispose();
c) A == null;
- GC回收资源是靠将资源标记成(Generation)代的概念,0代则直接回收,1代减一变成0代,2代回收时减一变成1代。
- GC.Collect()就是手动将资源标记的方法,但是即使是0代也未必立即回收释放。
这里关于Generation的概念再补充一下(谢谢@大家都不容易 的提醒):
引入MSDN的解释:
It is recommended, but not required, that garbage collectors support object aging using generations. A generation is a unit of measure of the relative age of objects in memory. The generation number, or age, of an object indicates the generation to which an object belongs. Objects created more recently are part of newer generations, and have lower generation numbers than objects created earlier in the application life cycle. Objects in the most recent generation are in generation 0.
http://msdn.microsoft.com/en-us/library/system.gc(v=vs.110).aspx
一下子还真找不出更多的资料,因为这些概念都是从各种各样的书中获得的知识,然后加以自己的理解。其中《编写高质量的代码---改善C#程序的157个建议》中有阐述这个问题。
另:纠正下自己的英文错误,是Generation而不是Generate,慢慢来吧。
针对线程安全问题的优化
首先十分感谢@冰麟轻武的提醒,之前还真没有考虑到线程安全的问题,因为这方面的应用场景还没有遇到,不过还是学习了@冰麟轻武的Dispose模式之后受益匪浅。
线程安全是指多个线程同时操作一个实例(Instance)使用其资源时产生的共享资源状态问题,也就是说线程A/B释放了M的资源,而线程B/A同时也要求释放资源,这样导致资源重复释放,严重时调用了空对象的方法,导致错误,所以,如果是以上场景的话,就要考虑线程安全这个问题。
我个人又对@冰麟轻武这位仁兄的方法(https://code.csdn.net/snippets/112056)进行了修改,如果有错误的地方还请各位看官不吝指出。
/************************************************************* * Copyright @ BOCO Group * All Rights Reserved. * Author: Cui Yansong(STEPHEN-PC.Cuiyansong) * Mail: cuiyansong@boco.com.cn * Create Date: 5/23/2014 9:29:46 AM * File Name: DisposePatternBase * CLR Version: 4.0.30319.17929 * ***********************************************************/ using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; namespace BocodeProtobufTest.Common { /* ****************************************************************************************** * Note 1: 保证线程安全 * System.Threading.Interlocked.CompareExchange(T,T,T)此方法是为了保证线程安全所执行的原子操作。 * http://msdn.microsoft.com/en-us/library/bb297966.aspx * 首先将disposedMark标记为已释放,保证其他线程获得disposedMark值为已释放,其他线程将不执行该操作。 * 然后判断disposedMark标记之前的值是否为未释放,如果是则执行Dispose。 * * Note 2: 区分托管资源和非托管资源的释放方法 * 如果执行手动释放(直接调用类的Dispose方法),则需要将所有资源均释放调; * 如果程序执行析构函数,则无需释放托管资源,原因在于大多数情况下托管资源可自动释放并且当系统执行析 * 构方法时,已无法手动调用Dispose方法。 * ******************************************************************************************/ public abstract class DisposePatternBase : IDisposable { #region Public Properties #endregion Public Properties #region Private Properties /// <summary> /// A private property indicate whether resource have been diposed. /// </summary> /// <remarks> /// 0 --- Not Released /// 1 --- Released /// </remarks> private int disposedMark = 0; #endregion Private Properties #region Constructor /// <summary> /// 系统销毁对象时自动调用 /// </summary> ~DisposePatternBase() { // 原子操作,保证线程安全。 var original = System.Threading.Interlocked.CompareExchange(ref disposedMark, 1, 0); if (original != 0) return; Dispose(false); } #endregion Constructor #region Public Method /// <summary> /// 外部调用(手动)Dispose方法 /// </summary> public void Dispose() { // 原子操作,保证线程安全。 var original = System.Threading.Interlocked.CompareExchange(ref disposedMark, 1, 0); if (original != 0) return; Dispose(true); GC.SuppressFinalize(this); } protected abstract void Dispose(bool disposing); #endregion Public Method } public class DisposePatternDemo : DisposePatternBase { private SafeHandle handle; private IList<byte> source; protected override void Dispose(bool disposing) { if (disposing) { // Release managed resource if (source != null && source.Count != 0) { source = null; } } // Release unmanaged resource if (handle != null) handle.Dispose(); } } }
Reference
《.Net设计规范》(Framework Design Guideline)
http://msdn.microsoft.com/zh-cn/library/System.GC(v=vs.80).aspx
https://code.csdn.net/snippets/112056
http://baike.baidu.com/view/1298606.htm?fr=aladdin