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方法,但也不至于使得非托管资源得不到释放。代码如下:

时间: 2024-12-19 17:26:38

C#编程(七十四)----------释放非托管资源的相关文章

[转]在C#中使用托管资源和非托管资源的区别,以及怎样手动释放非托管资源:

托管资源指的是.NET可以自动进行回收的资源,主要是指托管堆上分配的内存资源.托管资源的回收工作是不需要人工干预的,有.NET运行库在合适调用垃圾回收器进行回收. 非托管资源指的是.NET不知道如何回收的资源,最常见的一类非托管资源是包装操作系统资源的对象,例如文件,窗口,网络连接,数据库连接,画刷,图标等.这类资源,垃圾回收器在清理的时候会调用Object.Finalize()方法.默认情况下,方法是空的,对于非托管对象,需要在此方法中编写回收非托管资源的代码,以便垃圾回收器正确回收资源. 在

.net 资源释放(托管资源和非托管资源)

1.托管资源 像int.float.DateTime等都是托管资源:net中80%的资源都是托管资源: 托管资源的回收通过GC(垃圾回收器)自动释放分配给该对象的内存,但无法预测进行垃圾回收的时间,我们无法控制系统在什么时间回收资源. 2.非托管资源 像ApplicationContext,Brush,Component,ComponentDesigner,Container,Context,Cursor,FileStream,Font,Icon,Image,Matrix,Object,Odbc

【读书笔记】C#高级编程 第十四章 内存管理和指针

(一)后台内存管理 1.值数据类型 Windows使用一个虚拟寻址系统,该系统把程序可用的内存地址映射到硬件内存中的实际地址,该任务由Windows在后台管理(32位每个进程可使用4GB虚拟内存,64位更多,这个内存包括可执行代码和加载的DLL,以及程序运行时使用的变量内容). 在处理器的虚拟内存中,有一个区域称为栈.栈存储不是对象成员的值数据类型. 释放变量时,其顺序总是与它们分配内存的顺序相反,这就是栈的工作方式. 程序第一次运行时,栈指针指向为栈保留的内存块末尾.栈实际上是向下填充的,即从

说说非托管资源的回收

释放未托管的资源有两种方法 1.析构函数 2.实现System.IDisposable接口 一.析构函数 构造函数可以指定必须在创建类的实例时进行的某些操作,在垃圾收集器删除对象时,也可以调用析构函数.析构函数初看起来似乎是放置释放未托管资源.执行一般清理操作的代码的最佳地方.但是,事情并不是如此简单.由于垃圾回收器的运行规则决定了,不能在析构函数中放置需要在某一时刻运行的代码,如果对象占用了宝贵而重要的资源,应尽可能快地释放这些资源,此时就不能等待垃圾收集器来释放了. 实例 C# 代码   复

.net非托管资源的回收

释放未托管的资源有两种方法 1.析构函数 2.实现System.IDisposable接口 一.析构函数 构造函数可以指定必须在创建类的实例时进行的某些操作,在垃圾收集器删除对象时,也可以调用析构函数.析构函数初看起来似乎是放置释放未托管资源.执行一般清理操作的代码的最佳地方.但是,事情并不是如此简单.由于垃圾回收器的运行规则决定了,不能在析构函数中放置需要在某一时刻运行的代码,如果对象占用了宝贵而重要的资源,应尽可能快地释放这些资源,此时就不能等待垃圾收集器来释放了. 实例 C# 代码   复

利用IDisposable接口构建包含非托管资源对象

托管资源与非托管资源 在.net中,对象使用的资源分为两种:托管资源与非托管资源.托管资源由CLR进行管理,不需要开发人员去人工进行控制,.NET中托管资源主要指"对象在堆中的内存":非托管资源指对象使用到的一些托管内存之外的内资源(例如操作系统的资源),CLR不会管理这些资源,需要开发人员去控制..NET对象使用到的非托管资源主要有I/O流.数据库连接.Socket连接.窗口句柄等直接与操作系统操作的相关资源. 管理非托管资源 当一个对象不再使用时,我们应该将它使用的非托管资源释放掉

linux内核奇遇记之md源代码解读之十四raid5非条块内读

转载请注明出处:http://blog.csdn.net/liumangxiong 如果是非条块内读,那么就至少涉及到两个条块的读,这就需要分别从这两个条块内读出数据,然后再凑成整个结果返回给上层.接下来我们将看到如何将一个完整的bio读请求拆分成多个子请求下发到磁盘,从磁盘返回之后再重新组合成请求结果返回给上层的. 4097 logical_sector = bi->bi_sector & ~((sector_t)STRIPE_SECTORS-1); 4098 last_sector =

shell编程(十四)--- until循环

until循环语法格式: until CONDITION do     statement done 说明: until进入循环的条件是:condition不成立时,就执行循环. until进入循环的条件正好和while相反,while进入循环的条件是:condition成立时,就进入循环. 示例1:while循环 [[email protected] Learn]# cat while.sh  #!/bin/bash declare -i sum=0 declare -i i=0 while 

七十四、expect脚本同步文件、expect脚本指定host和要同步的文件、构建文件分发系统、

七十四.expect脚本同步文件.expect脚本指定host和要同步的文件.构建文件分发系统.批量远程执行命令 一.expect脚本同步文件 自动同步文件,在一台机器上同步文件到另一台机器上去.核心命令是rsync [[email protected] sbin]# vim 4.expect     路径:/usr/local/sbin/ #!/usr/bin/expect set passwd "1346" spawn rsync -av [email protected]:/tm