收藏 分享(赏)

C#基础知识梳理系列十二:终结操作及资源清理.doc

上传人:j35w19 文档编号:6621747 上传时间:2019-04-18 格式:DOC 页数:9 大小:91KB
下载 相关 举报
C#基础知识梳理系列十二:终结操作及资源清理.doc_第1页
第1页 / 共9页
C#基础知识梳理系列十二:终结操作及资源清理.doc_第2页
第2页 / 共9页
C#基础知识梳理系列十二:终结操作及资源清理.doc_第3页
第3页 / 共9页
C#基础知识梳理系列十二:终结操作及资源清理.doc_第4页
第4页 / 共9页
C#基础知识梳理系列十二:终结操作及资源清理.doc_第5页
第5页 / 共9页
点击查看更多>>
资源描述

1、摘 要经过上一章的讨论,我们知道,CLR 会在必要的时候启动垃圾回收器对不再使用的对象所占的内存进行回收,其实,在一个对象被回收前我们还可以通过析构函数来实现终结操作释放资源,了解终结操作后,我们还可以使用 Dispose 模式进行手工强制清理资源。这一章我们将讨论这些相关话题。第一节 析构函数和 Finalize 方法C#与 C+有着类似的析构函数,都是对资源进行清理,但是,在 C+中,开发人员明确知道析构函数会被调用,而 C#中,开发人员不太明确析构函数会在什么时候被调用,它是由 CLR 管理的,通常是在一个对象被标记为垃圾对象,如果有析构函数,CLR 的垃圾回收器会先调用析构函数,然后再

2、回收其内存。类型 System.Object 有一个受保护的虚方法 protected virtual void Finalize();这个就是“析构函数”。如果想为一个类型添加析构函数,必须使用与 C+类型的语法结构:前置波浪线+ 类名,相当于无参构造函数的名前加上波浪线,如下:public class Code_12 : IApppublic void DoWork()Code_12()Console.WriteLine(“Clear Code_12“);析构函数前不能有任何访问修饰符,并且一个类型只能有一个析构函数。编译后,上面的Code_12()被编译成名为 Finalize 的方法可

3、以看到,编译过程实际上是对基类 Object 的虚方法 Finalize()的重写,可以非常强悍地认为 Finalize 就是析构函数Code_12()的别名,http:/ 二者只是书写方式不同,干的都是一样的擦屁股的活。我们再来看一下它的内部 IL:.method family hidebysig virtual instance void Finalize() cil managed/ 代码大小 25 (0x19).maxstack 1.tryIL_0000: nopIL_0001: ldstr “Clear Code_12“IL_0006: call void mscorlibSyste

4、m.Console:WriteLine(string)IL_000b: nopIL_000c: nopIL_000d: leave.s IL_0017 / end .tryfinallyIL_000f: ldarg.0IL_0010: call instance void mscorlibSystem.Object:Finalize()IL_0015: nopIL_0016: endfinally / end handlerIL_0017: nopIL_0018: ret / end of method Code_12:Finalize可以看到,Finalize()方法内实际上是将代码包装到

5、tryfinally 块内,我们实现的代码被放到了 try 块,在 finally 块内调用了基类的 Finalize 方法,相当于 base.Finalize()。前面我们说过,http:/ 析构函数是在垃圾回收器回收垃圾对象之前的最后才执行一些清理工作,它的执行是受 CLR 管理,非人工可控,我们通过一个示例代码来看一下它的执行顺序:public class Code_12_01public Code_12_01()Console.WriteLine(“Create Code_12_01“);Code_12_01()Console.WriteLine(“Clear Code_12_01“)

6、;public class Code_12_02 : Code_12_01public Code_12_02()Console.WriteLine(“Create Code_12_02“);Code_12_02()Console.WriteLine(“Clear Code_12_02“);public class Code_12_03 : Code_12_02public Code_12_03()Console.WriteLine(“Create Code_12_03“);Code_12_03()Console.WriteLine(“Clear Code_12_03“);执行以下代码,先创建,

7、再遗弃,最后垃圾回收:public void DoWork()Code_12_03 temp = new Code_12_03();temp = null; /遗弃对象,等待垃圾回收GC.Collect();打印结果:Create Code_12_01Create Code_12_02Create Code_12_03Clear Code_12_03Clear Code_12_02Clear Code_12_01在调用析构函数过程中,是从派生类逐级向上调用基类的析构函数。CLR 对这种在垃圾回收前调用对象的析构函数进行资源清理的工作就是终结操作。我们不能控制垃圾回收,同样也不能控制终结操作,调

8、用 GC.Collect();只是向 CLR 发出垃圾回收的请求,垃圾回收器对一第列对象回收的先后顺序,我们是无法控制的。第二节 终结操作在我们平时开发工作中,或多或少都会用到本地资源,如打开文件,网络连接等,对这些资源的操作实际上是 CLR 通过 Windows 获取资源的独占句柄,使用完后,必须释放句柄,否则其他访问者将无法使用,例如我们经常碰到的异常“文件 XXX 正常由另一进程使用,因此该进程无法访问该文件。”就是这种原因造成的。终结操作就是利用析构函数来释放/清理资源,在对象被垃圾回收前,CLR 调用 Finalize()方法做清理工作,前提是我们提供了析构函数。这通常在我们定义类型

9、中使用到本地资源的时候非常有用。如下一个文件管理类:public class FileManagerFileStream fs = null;public FileManager()FileManager()if (fs != null)fs.Close();在调用析构函数中,应该确保其内部不该出异常,其实前一节中Code_12()的 IL 代码也可以看出来,并没有与 try 对应的 catch 块。所以我们在FileManager()内对 fs 进行了判空。垃圾回收器发现 FileManager 对象不再可用时,会调用 Finalize(),在内部关闭fs,接着就是回收其内存了。通常在有垃圾

10、回收的时候都有可能调用 Finalize()方法。事实上,在实现了 Finalize()的对象内存被回收过程并不是如此简单,这个终结操作有时可能须要执行两次或更多次垃圾回收才能达到释放其内存的目的,继续往下看。先来看两个垃圾回收器管理的列表:终结列表(Finalization List):放置所有实现了 Finalize()方法的对象的指针。Frachable 队列:放置已被认定为垃圾对象且实现了 Finalize()的对象的指针,这里的指针是从终结列表中移过来的。如果类型实现了 Finalize(),在创建该类型对象前,即调用构造器之前,CLR 会将该对象的一个指针放到一个终结列表(Fina

11、lization List)中,终结列表是由垃圾回收器管理的一个数据结构。在一次垃圾回收前,垃圾回收器会标记所有的不可达对象,这时所有的不可达对象已经被判了“死刑”,接着扫描终结列表以查找实现了 Finalize()方法的对象的指针,并将这些指针从终结列表中移到 Frachable 队列中,当 Frachable 队列不为空时,CLR有一个专门的线程来负责调用队列中指针对应对象的 Finalize 方法,为了能够调用这些对象的 Finalize()方法,必须重新激活这些对象,也就是从“不可达”状态变成“可达”状态,这个过程是使对象复活的过程,调用完 Finalize()方法后,该对象就彻底完蛋

12、了,接着就是等待下一次垃圾回收时对其内存进行回收,它永远再无出头之日了(当然,如果在析构函数中把该对象的指针放到一个其他静态变量中,那情况就不一样了。有兴趣的可以自己测试一下。)。在这个过程中,假如对象已被标记为垃圾,但未调用其 Finalize 方法前,该对象可能被从第 0 代提升为第 1 代,则有可能要经过更多次垃圾回收才能释放其内存。GC 类提供了一个静态方法 GC.WaitForPendingFinalizers(), 此方法将挂起当前线程,接着垃圾回收器清空 Frachable 队列并调用其中对象的Finalize()方法,全部调用完毕后被挂起的线程才得以恢复。前面的描述中我们知道,

13、即使调用了 Finalize()方法也不一定能立即释放该对象的内存,所以可以在 GC.WaitForPendingFinalizers()方法后立即跟一个 GC.Collect()进行回收,但微软不建议我们不这么干。第三节 使用 Dispose 模式在上面的讨论中,我们知道 Finalize 方法是受 CLR 管控的,也就是一个类型的本地资源在什么时候得到清理,我们并不太清楚,有没有一种方法可以让我们对其方便地控制呢?当然有!那就是实现 Dispose 模式。接口 System.IDisposeable 提供了对 Dispose 模式实现的最佳实践。public interface IDisp

14、osablevoid Dispose();通过实现这个接口,我们可以方便规范地来管理包装了本地资源的类对象,这个模式起到了双重保险的作用,可以分别清理托管和非托管资源。像TextReader、FileStream 、WinForm 窗体都实现了这个接口,这里我们推荐 MSDN 上面的实现方式。我们继续对上面的 FileManager 类进行改造:public class FileManager : IDisposableprivate bool disposed = false;/非托管资源private IntPtr handle;/托管资源FileStream fs = null;publ

15、ic FileManager()FileManager()/GC 调用,终结Dispose(false);public void Dispose()/显示关闭Dispose(true);/通知 GC 不必再调用 Finalize()GC.SuppressFinalize(this);protected virtual void Dispose(bool disposing)if (!this.disposed)if (disposing /释放非托管资源/.handle = IntPtr.Zero;disposed = true;除了实现接口的 Dispose()方法,还创建了一个通用的方法

16、Dispose(bool disposing) ,在此方法内,如果对象已经被清理,则不再做清理工作。有两点要注意:(1)Dispose()方法内部,传递了一个 true 值告诉 Dispose(bool disposing)方法此次调用是手工调用,接着使用一个静态方法 GC.SuppressFinalize(this);告诉垃圾回收器,此对象我已经手工调用清理资源的方法,你不必再调用 Finalize()方法了。(2)FileManager() 方法内是传一个 false 值告诉 Dispose(bool disposing)此对象是通过垃圾回收器清理资源的。也就是说,对于 FileManag

17、er 对象,如果你手工调用了清理的方法,则会对托管和非托管方法进行清理,如果你忘记了手工调用,垃圾回收器还是会通过调用 Finalize()方法对非托管资源进行清理。第四节 使用 using一般地,对于实现了 IDisposeable 接口的类型对象,在使用完毕后我会在 finally 块中调用 Dispose 方法来释放资源。如下:FileManager fs = null;try/fs.finallyif (fs != null)fs.Dispose();其实也可以以一种更简洁的方法来实现,那就是使用 using 语句,如下:public void Test()FileManager fs

18、 = null;using (fs = new FileManager()Console.WriteLine(fs.ToString();我们来看一下编译器生成的 IL 是什么样的?.method public hidebysig instance void Test() cil managed/ 代码大小 45 (0x2d).maxstack 2.locals init (0 class ConsoleApp.Example12.FileManager fs,1 class ConsoleApp.Example12.FileManager CS$3$0000,2 bool CS$4$0001

19、)IL_0000: nopIL_0001: ldnullIL_0002: stloc.0IL_0003: newobj instance void ConsoleApp.Example12.FileManager:.ctor()IL_0008: dupIL_0009: stloc.0IL_000a: stloc.1.tryIL_000b: nopIL_000c: ldloc.0IL_000d: callvirt instance string mscorlibSystem.Object:ToString()IL_0012: call void mscorlibSystem.Console:Wr

20、iteLine(string)IL_0017: nopIL_0018: nopIL_0019: leave.s IL_002b / end .tryfinallyIL_001b: ldloc.1IL_001c: ldnullIL_001d: ceqIL_001f: stloc.2IL_0020: ldloc.2IL_0021: brtrue.s IL_002aIL_0023: ldloc.1IL_0024: callvirt instance void mscorlibSystem.IDisposable:Dispose()IL_0029: nopIL_002a: endfinally / end handlerIL_002b: nopIL_002c: ret / end of method Code_12:Test可以看到,编译器对于 using 语句是按照 tryfinally 块模式来处理,并且在 finally 块中调用了 Dispose 方法,与上面我们手工编写的 tryfinally 块代码实现了相同的功能。在类似的环境中,using 语句只能应用于实现了 System.IDisposable 接口的类型(或值类型)对象。

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 企业管理 > 管理学资料

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报