C#编程(七十四)----------释放非托管资源

释放非托管资源

在介绍释放非托管资源的时候,我觉得有必要先来认识一下啥叫非托管资源,既然有非托管资源,肯定有托管资源.

 

 

托管资源指的是.net可以自棕进行回收的资源,主要是指托管堆上分配的内存资源.托管资源的回收工作是不需要人工干预的,有.net运行库在合适的调用垃圾回收器进行回收.

 

非托管资源指的是.net不知道如何回收的资源,最常见的一类非托管资源是包装操作系统资源的对象,例如文件,窗口,网络连接,数据库连接,画刷,图标等.这类资源,垃圾回收器在清理的时候会调用Object.Finalize()方法.默认情况下,方法是空的,对于非托管资源,需要在此方法中编写回收非托管资源的代码,以便垃圾回收器正确回收资源.

 

 

在.net中,Object.Finalize()方法是无法重载的,编译器是根据类的析构函数来自动生成Object.Finalize()方法的,所以对于包含非托管资源的类,可以将释放非托管资源的代码放在析构函数中.

 

注意,不能再析构函数中释放托管资源,因为析构函数是由垃圾回收器调用的,可能在析构函数调用之前,类包含的托管资源已经被回收了,从而导致无法预知的结构.

 

 

本来呢,如果按照上面做法,非托管资源也能够由垃圾回收器进行回收,但是非托管资源一般是有限的,比较宝贵的,而垃圾回收器是由CLR(不知道CLR的看我上一篇文章)自动调用的,这样就无法保证一级的释放非托管资源,因此定义了一个Dispose()方法,让使用者能够手动的释放非托管资源.Dispose()方法是防雷的托管资源和非托管资源,使用者手动调用此方法后,垃圾回收期不会对此类实例再次进行回收.Dispose()方法是有使用者调用的,在调用时,类的托管资源和非托管资源肯定都还没有被回收,所以可以同时回收这两者资源.

 

微软为非托管资源的回收专门定义了一个接口:IDisposable,接口中只能包含一个Dispose()方法.任何包含非托管资源的类,都应该继承此接口.

 

 

在一个包含非托管资源的类中,关于资源释放的标准做法是:

(1)  继承IDisposeable接口;

(2) 事项Dispose()方法,在其中释放托管资源和非托管资源,并将对象本身从垃圾回收器中移除(垃圾回收器不在回收此资源);

(3) 实现类析构函数,在其中释放非托管资源

 

 

在使用的时候,显示调用Dispose()方法,可以及时的释放资源,同时通过移除Finalize()方法的执行,提高了性能;如果没有显示调用Dispose()方法,垃圾回收器也可以通过析构函数来释放非托管资源,垃圾回收器本身就具有回收托管资源的功能,从而保证资源的正常释放,只不过由垃圾回收器回收会导致非托管资源的未能及时释放.

 

在.net中应该尽可能的少使用析构函数释放资源.在没有析构函数的对象在垃圾处理器一次处理中从内存删除,但有析构函数的对象,需要两次,第一次调用析构函数,第二次删除对象.而且在析构函数中包含大量的释放资源代码,会降低垃圾回收器的工作项链,影响性能.所以对于包含非托管资源的对象,最好及时的调用Dispose()方法来回收资源,而不是依赖垃圾回收器.

 

上面就是.net中对包含非托管资源的类的资源释放几只,只要按照上述的步骤编写代码,累就属于资源安全的类.

案例:

class MyResourceWrapper:IDisposable

{

    public void Dispose()

    {

        Console.WriteLine("release resources with Dispose");

        Console.Beep();

    }

}

 

class Program

{

    static void Main(string[] args)

    {

        MyResourceWrapper mr = new MyResourceWrapper();

        mr.Dispose();

    }

}

 

 

当我们显示调用Dispose方法的时候,可以听到系统的蜂鸣声.

 

注意:通多Dispose进行资源的释放也是有潜在的风险的,因为Dispose方法需要被程序员显示的滴啊用,如果代码中漏掉了Dispose的调用或者在Dispose调用之前产生了异常从而没有指定Dispose,那么有些资源可能就一直留在内存中了.

 

所以我们应该使用下面的方式保证Dispose方法可以被调用到:

static void Main(string[] args)

{

    MyResourceWrapper mr = new MyResourceWrapper();

    try

    {

        //do something wiht mr object

    }

    finally

    {

        mr.Dispose();

    }

}

 

 

但是,每次编写Idspose的代码都是用try块会觉得很麻烦,还好C#中,我们可以重用using关键字来简化Dispose的调用.

 

重用using关键字

在C#中,using语句提供了一个高效的调用对象Dispose方法的方式.对于任何IDisposable接口的类型,都可以使用using语句,而对于那些没有实现IDisposable接口的类型,使用using语句会导致一个编译错误.

 

static void Main(string[] args)

{

    using (MyResourceWrapper mr = new MyResourceWrapper())

    {

        //do something with mr object

    }

}

 

在using语句块结束的时候,mr实例的Dispose方法会被自动调用,using语句不仅免除了程序员输入Dispose调用的代码,他还保证Dispose方法被调用,无论using语句块是否顺利执行. 事实上,C#编译器为using语句自动添加了try/finally语句块.

 

 

 

.NET提供了两种释放非托管资源的方式,一种是Finalize方法和Dispose方法.

 

接下来再来看Finalize方法

在.net的基类System.Object中,定义了名为Finalize()的虚方法,这个方法默认什么都不做.我们可以为自定义的类型重写Finalize方法,在该方法中加入必要的非托管资源清理逻辑.当要从内尊中删除这个类型的对象时,垃圾回收器会调用对象的Finalize方法.所以,无论.net进行一次自发的垃圾回收,还是通过GC.Collect()进行强制垃圾回收,Finalize方法总是会被调用.另外,当承载应用程序的AppDomain从内存中移除时,同样会调用Finalize方法.

 

重写Finalize方法

假设我们现在有一个是由非托管资源的类型,我们就需要重写Finalize方法来进行非托管资源的清理,但是当通过下面的方式重写Finalize方法的时候,会出现编译错误:

    class MyResourceWrapper 

    {

        protected override void Finalize()

        { }

}

 

其实,当我们想要重写Finalize方法时,C#为我们提供了(类似C++)析构函数语法(C#终结器)来重写该方法.C#终结器和构造函数语法类似,方法名和类型名一样;不同的是,终结器具有~前缀,并且不能使用访问修饰符,不接受参数,不能重载,所以一个类只能有一个终结器.

 

    class MyResourceWrapper

    {

        ~MyResourceWrapper()

        {

            Console.WriteLine("release unmanaged resources");

            Console.Beep();//t通过控制台播放器播放提示音

        }

    }

 

之所以C#只支持这种方式进行Finalize方法的重写,是因为C#编译器回味Finalize方法隐式的加入一些必要的基础代码.添加的那些基础代码我就不给你们看了,反正就是保证finalize方法总是能被执行.

 

 

当我们执行下面的代码的时候,我们就可以听到蜂鸣声了:

MyResourceWrapper mr = new MyResourceWrapper();

 

 

Finalize的工作机制

他的工作机制很复杂,想要深入研究,您可以自己上网查看(估计没人查),我只是参考网上一些比较简单的说法来理解Finalize的工作机制.

其实,Finalize方法的调是相当耗费资源的,Finalize方法的作用是保证.net对象能够在垃圾回收时清零非托管资源,如果创建了一个不使用非托管资源的类型,实现终结器是没有任何作用的,所以,如果没有特殊的需要应该避免重写Finalize方法.

 

 

 

 

Dispose和Finalize的结合

 

Finalize可以通过垃圾回收进行自动的调用,而Dispose需要被代码显示的调用.so,为了保险起见,对于一些非托管资源,还是有必要实现终结器的(什么是终结器呢,看最后),也就是说,如果我们忘记了显式的调用Dispose,那么垃圾回收也会调用Finalize,从而保证非托管资源的回收.

 

在MSND中提供了一种很好的模式来实现IDisposable接口来结合Dispose和FInalize,案例如下:

    class MyResourceWrapper:IDisposable

    {

        private bool IsDisposed = false;

        ~MyResourceWrapper()

        {

 

            Dispose(false);           

 

        }

        public void Dispose(bool disposing)

        {

            if (!this.IsDisposed)

            {

                if (disposing)

                {

                    //清除托管资源

                }

                //清除非托管资源

            }

            this.IsDisposed = true;

        }

        public void Dispose()

        {

            Dispose(true);

            //告诉GC不调用Finalize方法

            GC.SuppressFinalize(this);

        }

}

 

在这个模式中,void Dispose(bool disposing)函数通过一个disposing参数来区别当前是否是被Dispose()调用.如果是被Dispose()调用.如果是被Dispose()调用,那么需要同时释放托管和非托管资源.如果是被终结器调用了,那么只需要释放非托管的资源即可.Dispose()函数是被其他代码显式调用应要求释放资源的,而Finalize是被GC调用的.

 

另外,由于在Dispose()中已经释放了托管和非托管的资源,因此在对象中被GC回收时再次调用Finalize是没有必要的,所以在Dispose()中调用GC.SuppressFinalize(this)避免重复调用Finalize.同样,因为IsDisposed变量的存在,资源只会被释放一次,多余的调用会被忽略.

 

这个模式的优点如下:

1.如果没有显式的调用Dispose(),未释放托管和非托管资源,那么在垃圾回收时,还会执行Finalize,释放非托管资源,同时GC会释放托管资源.

2.如果调用了Dispose(),就能及时释放托管和非托管资源,那么该对象被垃圾回收时,就不会执行Finalize(),提高了非托管资源的使用效率并提升了系统性能.

 

 

 

 

终结器(finalizer)主要用于C#.Net中的非托管代码清理中,通常同时实现终结器和Dispose方式。这样对于细心的使用者直接显示调用Dispose方法会提高垃圾回收的性能,对于粗心的使用者虽然忘记了调用Dispose方法,但也不至于使得非托管资源得不到释放。代码如下:

posted on 2017-03-29 11:33  Sun‘刺眼的博客  阅读(413)  评论(0编辑  收藏  举报

导航