CLR内存管理之释放非托管资源

来源:互联网 发布:金门炮弹菜刀店淘宝 编辑:程序博客网 时间:2024/06/03 08:12

上篇文章中我提到,CLR通过栈指针给变量分配内存空间,通过GC来释放不再引用的内存空间。GC虽然减少了程序员处理内存的困难,但它也有局限性,它不能处理像文件句柄、网络连接、数据库连接这样的非托管资源。在定义一个类时,我们使用两种机制来自动释放非托管资源:

1,声明一个析构函数(或终结器finalizer),作为类的一个成员

2,在类中实现System.IDisposable接口

析构函数:

析构函数的声明格式如下

复制代码
class Hotel{       ~Hotel()        {            //destructor implementation        }}
复制代码

在GC销毁对象之前,会调用对象的析构函数。C#编译器在编译析构函数时,会隐式地把析构函数的代码编译为等价于Finalize()方法的代码,从而确保执行父类的Finalize()方法。下面模拟了编译器编译析构函数生成的c#代码:

复制代码
protected override void Finalize(){     try      {         //destructor implementation      }      finally      {           base.Finalize();      }}
复制代码

如上所示,在~Hotel()析构函数中的实现代码封装在Finalize()方法的一个try语句块中。对父类的Finalize()方法的调用放在finally块中,确保该调用的执行。
有经验的C++开发人员会大量使用析构函数,用于清理资源,提供调试信息或执行一些其他的操作。这是因为,C++中,销毁对象时,它的析构函数会立即执行。但是,C#中,我们使用析构函数的次数却少之又少。C#析构函数的问题在于它的不确定性,由于GC的工作方式,我们无法确定C#析构函数何时会执行。因此,我们不敢在析构函数中放置一些需要在特定时刻执行的代码。对象如果占用了宝贵而重要的资源,应该尽快释放这些资源,此时就不能指望GC来自动清理资源了。另一个问题是,C#析构函数的实现会延迟对象从内存中删除的时间。没有析构函数的对象会在GC的一次处理中从内存中删除。但有析构函数的对象却需要两次的处理才能销毁。第一次调用不会删除,第二次调用时才会执行删除操作。另外,CLR使用一个线程来执行所有对象的Finalize()方法,如果频繁使用析构函数,并让它们执行长时间的清理任务,对性能的影响就会非常显著。

实现IDisposable接口:

在C#中,推荐使用System.IDisposable接口代替析构函数。IDisposable接口为释放非托管资源提供了一种确定的机制,并避免产生析构函数固有的与GC相关的问题。IDisposable接口的实现如下:

复制代码
class Hotel: IDisposable {        public void Dispose()        {            //implementation                    }}
复制代码

Dispose()方法的实现代码显式地释放由对象直接使用的非托管资源,并在所有也实现IDisposable接口的封装对象上调用Dispose()方法。这样,Dispose()方法就为释放非托管资源提供了一种精确的控制。
看下面的代码:

Hotel hotel = new Hotel();hotel.Dispose();

该代码会调用hotel对象的Dispose()方法,但是如果执行过程出现异常,这段代码就不会释放对象使用的资源。所以,我们对它加以改进:

复制代码
            Hotel hotel = null;            try            {                hotel = new Hotel();            }            finally            {                if (hotel != null)                {                    hotel.Dispose();                }            }
复制代码

这样就能确保,即使执行过程出现异常,也总能保证在hotel上调用Dispose()方法。但是如果我们的项目中重复使用这样的结构,代码就会变得非常杂乱。C#提供了一种语法,使用using关键字,生成与上面等同的代码:

           using (Hotel hotel = new Hotel())            {                //operation            }

是不是很熟悉,我们在使用数据库连接和网络连接时经常用到这种写法。它也可以保证在出现异常时调用Dispose()方法。
在实际应用中,我们需要综合使用以上两种方法来进行非托管资源的释放工作:

class Hotel: IDisposable
   {
       private bool isDisposed = false;
 
       ~Hotel()
       {
           Dispose(false);
       }
 
       public void Dispose()
       {
           Dispose(true);
           GC.SuppressFinalize(this);
       }
 
       protected virtual void Dispose(bool disposting)
       {
           if (!isDisposed)
           {
               if (disposting)
               {
                   //Cleanup managed objects by calling their Dispose()
               }
               //Cleanup unmanaged objects
           }
           isDisposed = true;
       }
       public void Operate()
       {
           if (isDisposed)
           {
               throw new ObjectDisposedException("Hotel");
           }
           //implementation
       }       
   }

在该示例中,Dispose(bool disposting)方法执行真正的清理工作。它接受一个参数disposting,用来标示调用是来自Dispose()方法还是析构函数。在调用Dispose()方法时,我们用到了GC.SuppressFinalize(this);它会告诉GC,在调用当前对象的Dispose()方法时,不再执行对象的析构函数。在Operate()方法中,会根据变量isDisposed来确定对象是否已经被销毁,如果被销毁,会抛出一个ObjectDisposedException类型的异常。

0 0
原创粉丝点击