zoukankan      html  css  js  c++  java
  • C#.Net中的非托管代码清理

    帮助其它项目组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
    {
    /// 保证资源只用释放一次
    private bool _alreadyDisposed = false;
    /// 用来判断释放资源的类别(托管和非托管)
    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);
    }
    /// 保证资源只用释放一次
    private bool _alreadyDisposed = false;
    /// 用来判断释放资源的类别(托管和非托管)
    protected virtual void Dispose(bool isDisposing)
    {
    if(_alreadyDisposed)
    {
    return;
    }
    if(isDisposing)
    {
    //释放托管资源
    }
    //释放非托管资源
    _alreadyDisposed = true;
    }
    public void Dispose()
    {
    Dispose(true);
    //阻止GC把该对象放入终结器队列
    GC.SuppressFinalize(this);
    }
    }
  • 相关阅读:
    linux之sed用法【转载】
    关于Cookie和Session【转载】
    Oracle分页查询与RowNum
    fstream的用法
    Java:Date、Calendar、Timestamp的区别、相互转换与使用【转载】
    DatabaseMetaData的用法【转载】
    关于SQL的Group By
    【转载】B树、B-树、B+树、B*树都是什么
    Spring的MVC控制器返回ModelMap时,会跳转到什么页面?
    关于jsp中超链接的相对路径
  • 原文地址:https://www.cnblogs.com/sczw-maqing/p/3548731.html
Copyright © 2011-2022 走看看