编程中的资源管理(二)

来源:互联网 发布:广东省税务开票软件 编辑:程序博客网 时间:2024/06/16 10:33

一、上次讨论了C++,Java中的资源管理,现在讨论.NET中的Dispose模式、Using语句以及c++/cli中的确定性资源回收。

二、.NET中的Dispose模式

.NET中,也是使用垃圾收集来进行内存的管理,同样它也存在非内存资源的回收。为此.NET引入了Dispose模式。

1.       Dispose模式介绍

如果类A拥有操作系统资源或者很重要的状态如网络连接、数据库连接,而GC又不可能知道该何时回收这些资源,那么就需要类A提供类似CloseDispose方法给类A的客户,以便客户能够显示的、及时地释放这些资源。为此引入了IDispose接口,接口中仅包含一个方法,签名如下:

void Dispose()

        Dispose应满足的条件如下:

        可以安全的多次调用Dispose方法。

        需要释放对象拥有的所有资源。

        如果必要的话,调用基类的Dispose方法。

        Dispose方法不应该抛出异常。[C++的析构函数,Javafinally子句]

2.       例子代码

我们来看一个具体的例子:

using  System ;

using  System.IO ;

using  System.Text ;

using  System.Threading ;

public class DisposeTest {

        public static void Main(String[] arg) {

                FileStream  fs = null ;

                try {

                        fs  = File.OpenRead("c://boot.ini.bak") ;

            byte[] b = new byte[1024];

                        UTF8Encoding temp = new UTF8Encoding(true);

                while (fs.Read(b,0,b.Length) > 0)  {

               Console.WriteLine(temp.GetString(b));   

}

    

                }

                finally {

                        /*[1]*/

fs.Close() ;

                }

         /*[2]*/

Thread.Sleep(10000) ;   

        }

}

类的继承关系如下:

此时的IL代码如下:

.try

  {

    //省略了try块的IL代码

  }  // end .try

  finally

  {

    IL_0047:  nop

    IL_0048:  ldloc.0

    IL_0049:  callvirt   instance void [mscorlib]System.IO.Stream::Close()

    IL_004e:  nop

    IL_004f:  nop

    IL_0050:  endfinally

  }  // end handler

可以看出finally块儿内调用了Stream的虚函数Close来释放资源。

 

上述源代码的功能一目了然。请注意红色部分的两行代码。当程序执行[2]时,由于文件句柄已经通过[1]释放,所以可以改变boot.ini.bak的文件名,也可以删除文件。但是如果注释掉[1]处的释放资源代码,那么当当程序执行[2]时,将不可以修改文件名,因为没有释放文件句柄。这种情况下,仅在程序运行结束时,由GC在回收fs的内存时,执行FileStreamfinalizer方法而释放文件句柄。这种情况对于能够很快运行结束的程序而言,还是可以忍受的。但是如果是服务程序,或者长时间运行的程序,那么这将是一个严重的BUG

三、C#中的using语句

C#提供了一个更简洁的语法,using语句,将上面的代码使用using语句来实现,那么代码为:

using( FileStream fs = File.OpenRead("c://boot.ini.bak") ) {

            byte[] b = new byte[1024];

            UTF8Encoding temp = new UTF8Encoding(true);

            while (fs.Read(b,0,b.Length) > 0) {

                Console.WriteLine(temp.GetString(b));

            }                  

}

相应的IL为:

.try

  {

    //省略了try块的IL代码

  }  // end .try

  finally

  {

    IL_0045:  ldloc.0

    IL_0046:  ldnull

    IL_0047:  ceq

    IL_0049:  stloc.3

    IL_004a:  ldloc.3

    IL_004b:  brtrue.s   IL_0054

    IL_004d:  ldloc.0

    IL_004e:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()

    IL_0053:  nop

    IL_0054:  endfinally

  }  // end handler

可以看出using语句的经过编译之后,也相应的生成了finally子句,只不过对于编程人员来讲更容易些。很显然,using语句中的变量必须实现IDisposable接口。但是可以看出,using语句编译后的IL和通常的finally子句编译后的IL 有些不同。原因如下:

Stream类实现了接口IDispose [explicit interface method implementation],并将Dispose方法委托给Stream的虚函数CloseFileStreamoverride了虚函数Close

所以finallyIL代码虽然不一样,但是他们最终的处理逻辑是一样的。

如果将一的fs.Close()替换为((IDisposable)fs).Dispose(),那么两者的IL将是相同的。

四、C++/CLI中的确定性资源回收

我们看如下代码:

public ref class  ABC : public Object

{

public:

        ABC()   { Console::WriteLine("ABC") ;  }

        ~ABC() { Console::WriteLine("~ABC") ; }

        void fun2(){Console::WriteLine("fun2");}

protected:

        !ABC() { Console::WriteLine("!ABC") ; }

private:

        int a ;

};

虽然~ABC看起来就是ISO C++的析构函数,但是我们要知道在.NET中不存在析构函数,它将被编译成Dispose函数。!ABCISO C++中不存在,它被编译成Finalize函数。请参考下图:

此图说明:

1、  ABC实现了IDisposable接口,并且~ABC就是Dispose函数的实现。

2、  ABC就是Finalize函数的实现。

void TestNormalRelease()

{

        ABC ^ handle = gcnew ABC() ;

        delete handle ;

}

使用.NET类时,应该使用gcnew在托管堆中分配空间,并构造对象。对象使用 ^ 来标记句柄,也叫做Tracking Handle,释放对象资源时,还是使用delete运算符,它将调用对象的Dispose方法来释放资源。这是普通的释放资源语法,他没有使用C++/CLI的新的确定性资源清理特性。

函数的IL为:

IL_0002:  newobj     instance void ABC::.ctor()

  IL_0007:  stloc.1

  IL_0008:  ldloc.1

  IL_0009:  stloc.0

  IL_000a:  ldloc.0

  IL_000b:  brfalse.s  IL_0017

  IL_000d:  ldloc.0

  IL_000e:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()

IL_0002来构造对象,IL_000e来调用Dispose方法。

使用确定性资源清理特性的代码如下:

void TestDeterminateResourceReclaim()

{      

ABC stackObject ;

// use stackObject

}

可以看出,函数代码和普通的ISO C++stack object有相同的语法,相同的语义。唯一不同的是上述代码中的stackObject分配在托管堆中,而ISO C++中的stack object分配在函数的堆栈中。

函数的IL如下:

IL_0000:  ldnull

  IL_0001:  stloc.0

  IL_0002:  newobj     instance void ABC::.ctor()

  IL_0007:  stloc.1

  .try

  {

    IL_0008:  ldloc.1

    IL_0009:  stloc.0

    IL_000a:  leave.s    IL_0013

  }  // end .try

  fault

  {

    IL_000c:  ldloc.0

    IL_000d:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()

    IL_0012:  endfinally

  }  // end handler

  IL_0013:  ldloc.0

  IL_0014:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()

 

fault块是在try块发生异常的情况下执行的代码。可以看出无论函数TestDeterminateResourceReclaim正常退出,还是因为发生异常而退出,都可以保证释放了stackObject的所有资源。

注:

Visual C++ 2005 Express Edition Beta 2之后才支持确定性资源清理。

五、总结

由此可以看出不同的语言、不同的编程模型,使用了不同的方式以及时的准确的释放资源,保证程序的正确性、高效。相比而言C++/CLI的确定性资源清理提供了优雅的语法,语义,基本和ISO C++保持一致。不管使用何种方式,只要我们掌握了在确定环境下如何释放资源,并且按照该环境下的编程规范来管理资源就可以保证程序的正确性。

 

原创粉丝点击