您的位置:首页 > 编程语言

编程中的资源管理(二)

2005-06-09 09:03 148 查看
一、上次讨论了C++,Java中的资源管理,现在讨论.NET中的Dispose模式、Using语句以及c++/cli中的确定性资源回收。
二、.NET中的Dispose模式
在.NET中,也是使用垃圾收集来进行内存的管理,同样它也存在非内存资源的回收。为此.NET引入了Dispose模式。
1. Dispose模式介绍
如果类A拥有操作系统资源或者很重要的状态如网络连接、数据库连接,而GC又不可能知道该何时回收这些资源,那么就需要类A提供类似Close,Dispose方法给类A的客户,以便客户能够显示的、及时地释放这些资源。为此引入了IDispose接口,接口中仅包含一个方法,签名如下:
void Dispose()
Dispose应满足的条件如下:
⑴ 可以安全的多次调用Dispose方法。
⑵ 需要释放对象拥有的所有资源。
⑶ 如果必要的话,调用基类的Dispose方法。
⑷ Dispose方法不应该抛出异常。[同C++的析构函数,Java的finally子句]
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) ;
}
}
类的继承关系如下:
[/b]
此时的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的内存时,执行FileStream的finalizer方法而释放文件句柄。这种情况对于能够很快运行结束的程序而言,还是可以忍受的。但是如果是服务程序,或者长时间运行的程序,那么这将是一个严重的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的虚函数Close。FileStream类override了虚函数Close。
所以finally的IL代码虽然不一样,但是他们最终的处理逻辑是一样的。
如果将一的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函数。!ABC在ISO C++中不存在,它被编译成Finalize函数。请参考下图:
[/b]
[/b]此图说明:
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
}
[/b]可以看出,函数代码和普通的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++保持一致。不管使用何种方式,只要我们掌握了在确定环境下如何释放资源,并且按照该环境下的编程规范来管理资源就可以保证程序的正确性。

[/b]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: