重新学.Net[七]——垃圾回收和资源管理[下]

简介:
在前面说了GC的工作原理。需要注意的是,GC只能回收托管堆中的资源。其他一些非托管资源,比如文件资源,缓冲区,互斥体之类,无法通过GC自动回收。必须通过开发人员自己编程实现对其的回收(有时候会觉得CLR的资源管理也会比较麻烦,因为它有一部分自动的,有用一部分手动的,但和C++比比,我们应该很知足了^_^)。
很自然的一种编码方式是将回收资源的函数写入终结函数Finalize中,GC启动回收托管资源的时候顺便把非托管资源一并回收了(有时候我会简单得说GC回收了,指的就是在GC启动时正好执行该代码一并回收去了)。但是,这会带来很多的问题。最大的问题是时机问题。开发人员不能决定GC何时启动(包括调用GC.Collect()也不行),这使得无法确定非托管资源何时会被释放,也无法决定不同对象占用的资源的释放顺序。
因此,我们需要按照一种被称为Dispose的模式来进行非托管资源的管理,先看一段比较经典的Dispose模式编码:
public  class UnsafeSourceHolder : IDisposable 
        { 
                 private IntPtr buffer; 
                 private SafeHandler resource; 
                 private bool disposed; 
UnsafeSourceHolder() UnsafeSourceHolder() 
                { 
                        buffer = ...;    
                        resource = ...;    
                        disposed =  false
                } 
virtual void Dispose() virtual void Dispose(bool disposing) 
                { 
                         if (disposed) return; 
                        ReleaseBuffer(buffer); 
                         if (disposing) 
                        { 
                                 if (resource !=  null) resource.Dispose(); 
                        } 
                        disposed =  true
                } 
                ~UnsafeSourceHolder() 
                { 
                        Dispose( false); 
                } 
void Dispose() void Dispose() 
                { 
                        Dispose( true); 
                        GC.SuppressFinalize(this); 
                } 
void Close() void Close() 
                { 
                        Dispose(); 
                } 
void DoSomething() void DoSomething() 
                { 
                         if (disposed) throw  new ObjectDisposedException( "不能使用被释放的资源"); 
                } 
        }
从上面的代码中,我们来看看Dispose模式的关键之处。首先,如果一个类(上例中的UnsafeSourceHolder )中包含非托管资源(在上例中,buffer和resource均代表非托管资源),它需要实现 IDisposable接口,这个接口中只有一个public void Dispose()方法。当然,如果你不实现该接口(比如,把Dispose()中的内容放到Close()中去),在理论上是没有任何问题的。但,这样做至少有两个不好的地方:
1.你失去了和调用人说话的一种语言(我们默认调用人看到一个类实现了IDisposable接口就认为该类中含有非托管资源),调用人可能不知道这个类中包含非托管资源;
2.你失去了某些方便的语法支持(比如C#中的using会自动帮助你调用该接口的Dispose方法)。
因此这样做是必须的。而为什么还要有一个Close()方法呢,原因也有两个:
1.调用人会比较习惯Close()这样的命名;
2.CLR将Dispose()方法作为一个显性接口方法,你必须这样调用:(IDisposable i).Dispose(),比较麻烦。
恩,所以综合考虑,当一个类中有托管资源的时候,你需要实现IDisposable接口,并暴露上面两个API供调用者使用。
说到了调用,我们再来看看,一个调用该类对象的人,有几种触发资源回收的方式。一种就是显性的调用Dispose或Close方法(或利用语法偷偷调用),通知回收资源。另外一种,是让GC启动自动回收资源。什么?我说过GC无法回收非托管资源,恩,是的,但是我已经重载了(相当于)Finalize()方法。什么?你没看到,那么请注意~UnsafeSourceHolder()这个函数。这可不是C++的析构函数,在上例中写了此函数,相当于写如下代码:
override void Finalize() override void Finalize() 

    try 
    { 
        Dispose(); 
    } 
    finally 

        base.Finalize(); 
    } 
}
之所以按貌似析构函数的方法来写,是因为很多开发人员不会按规则写入上的代码,以至于终结函数抛出异常,导致进程Down掉,或资源泄露。因此,请你按这种让许多C++程序员会感到异常不爽的方式来写(我个人觉得这种设计有点画蛇添足了,一个不会写try-finally这样代码的开发人员,你如何确定它会写析构函数那样的代码??)。
但是但是,这样我们仔细看看代码我们可以发现。这两种调用方式是有所区别的,真实对非托管资源进行处理的是Dispose(bool disposing)函数,Dispose()方法调用Dispose(true),而GC调用的是Dispose(false)。这有什么区别呢?讲清楚这个问题,我们需要将非托管资源分成两类(这些是我自己的说法,可能会有bug哦^_^),一类是只被一个对象享用的非托管资源,在上例中以buffer表示。另一类是被许多对象享用的非托管资源,在上例中以resouce表示。自己独享的资源当此对象销亡时必须释放,所以在GC调用时会考虑释放该资源。但另一类被共享的资源则不能由GC来释放,因为GC释放次序是不定的,无法确认释放这个资源后是否有其他对象在使用该资源。而了解这个的,只有调用者自己。因此只能依赖于调用者自己手动调用Dispose()或Close()来释放该资源。也就是说,手动的调用,会释放该对象使用的所有非托管资源,而自动调用只能释放该对象独享的非托管资源。
但,如果调用者出现错误,在释放了非托管资源后,又再次使用它,该怎么办?这时候,要求对象能够告诉调用者:Are you craze?。在上例中,用disposed变量来记录资源的使用状态,在非法使用时抛出ObjectDisposedException异常(这也是Dispose模式推荐的一部分内容)。
在上面代码中,还有一句GC.SuppressFinalize(this);这时告诉GC不要自动回收该对象。这里的原因我也不是很清楚,有说是已显性回收该对象没必要再自动回收了。我个人觉得不对,因为显性回收的只是非托管资源而已,托管资源并未回收。查了下MSDN和一些书,觉得更好的解释应该是如果Dispose回收时正好执行垃圾回收会产生冲突(因为垃圾回收启动时导致的线程挂起,并不会影响非托管资源的回收,这样会从两个地方回收该资源产生冲突),所以要调用GC.SuppressFinalize使得垃圾回收机制会在一段时间内不再尝试回收该对象。请注意,这是一段时间内而不是永远。
上面就是Dispose模式的一个基本结构,简单总结一下有如下一些要点:
1.实现IDisposable接口。
2.提供Close()函数,内部调用Dispose()函数。
3.实现一个Dispose(bool disposing)函数,处理不同的非托管资源。
4.实现析构函数(加个伪字好了^_^),使其能在GC启动时自动回收。
5.当调用已释放资源时抛出ObjectDisposedException异常(同时要允许多次清除同一资源)。
6.使用GC.SuppressFinalize函数,防止清理冲突。
当然还可以根据情况进一步的改良它的实现(比如在资源回收时加锁),但它的整体思路和基本接口不应该改变。当你实现的类中有设计非托管资源的管理时,请按照该模式来实现。但,当你类中没有非托管资源的时候,请不要没事找事,想一想上中那个需要顿悟的地方哦^_^。









本文转自 duguguiyu 51CTO博客,原文链接:http://blog.51cto.com/duguguiyu/361650,如需转载请自行联系原作者
目录
相关文章
|
存储 开发框架 Java
【CLR C#】浅谈.Net的GC(垃圾回收)机制及其整体流程
在.NET程序开发中,为了将开发人员从繁琐的内存管理中解脱出来,将更多的精力花费在业务逻辑上,CLR提供了自动执行垃圾回收的机制来进行内存管理,开发人员甚至感觉不到这一过程的存在。.NET程序可以找出某个时间点上哪些已分配的内存空间没有被程序使用,并自动释放它们。自动找出并释放不再使用的内存空间机制,就称为垃圾回收机制。本文主要介绍.Net中的GC(垃圾回收)机制及其整体流程。
【CLR C#】浅谈.Net的GC(垃圾回收)机制及其整体流程
|
存储 开发框架 Java
|
监控 安全 Java
.NET垃圾回收
1 在.NET中的自动内存管理由垃圾回收器来执行,GC 全权负责对托管堆的内存管理. 2 对于开发人员来说,GC全权负责对内存的管理,监控和回收,我们应将更多的努力关于于非托管资源的清理方式的理解和应用上,以提升系统资源管理的性能和安全 一个对象不被任何外部对象引用时则被认定为垃圾。
609 0

热门文章

最新文章