释放非托管资源

在介绍释放非托管资源的时候,我觉得有必要先来认识一下啥叫非托管资源,既然有非托管资源,肯定有托管资源.

托管资源指的是.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(),提高了非托管资源的使用效率并提升了系统性能.

---------------------
作者:见证大牛成长之路
来源:CSDN
原文:https://blog.csdn.net/shanyongxu/article/details/47321007
版权声明:本文为博主原创文章,转载请附上博文链接!

5.C#释放非托管资源1的更多相关文章

  1. C# 释放非托管资源

    C#中资源分为托管资源和非托管资源. 托管资源由垃圾回收器控制如何释放,不需要程序员过多的考虑(当然也程序员也可以自己释放). 非托管资源需要自己编写代码来释放.那么编写好的释放非托管资源的代码(释非 ...

  2. 6.C# 释放非托管资源2

    C# 释放非托管资源 C#中资源分为托管资源和非托管资源. 托管资源由垃圾回收器控制如何释放,不需要程序员过多的考虑(当然也程序员也可以自己释放). 非托管资源需要自己编写代码来释放.那么编写好的释放 ...

  3. C#编程(七十四)----------释放非托管资源

    释放非托管资源 在介绍释放非托管资源的时候,我觉得有必要先来认识一下啥叫非托管资源,既然有非托管资源,肯定有托管资源. 托管资源指的是.net可以自棕进行回收的资源,主要是指托管堆上分配的内存资源.托 ...

  4. Dispose模式释放非托管资源

    实现方式用的是设计模式里的模板模式,基类先搭好框架,子类重写void Dispose(bool disposing) 即可. 需要注意的是基类的Finalize函数也就是析构函数调用的是虚函数void ...

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

    托管资源指的是.NET可以自动进行回收的资源,主要是指托管堆上分配的内存资源.托管资源的回收工作是不需要人工干预的,有.NET运行库在合适调用垃圾回收器进行回收. 非托管资源指的是.NET不知道如何回 ...

  6. IDisposable?释放非托管资源接口

    原文:https://www.cnblogs.com/luminji/archive/2011/03/29/1997812.html IDisposable高级篇:https://docs.micro ...

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

    1.托管资源 像int.float.DateTime等都是托管资源:net中80%的资源都是托管资源: 托管资源的回收通过GC(垃圾回收器)自动释放分配给该对象的内存,但无法预测进行垃圾回收的时间,我 ...

  8. C# 托管非托管资源释放

    1.C#几乎所有对象都为托管对象,不同点是有的对象封装了非托管资源. 2.C#大部分对象在进行垃圾回收时都可以回收,包括非托管资源,因为非托管资源都已经通过C#类进行了封装,会将非托管资源的释放放在析 ...

  9. .NET 清理非托管资源

    Dispose 类型的 Dispose 方法应释放它拥有的所有资源.它还应该通过调用其父类型的 Dispose 方法释放其基类型拥有的所有资源.该父类型的 Dispose 方法应该释放它拥有的所有资源 ...

随机推荐

  1. mtd工具

    http://daemons.net/linux/storage/mtd.html MTD The Memory Technology Devices (MTD) subsystem provides ...

  2. Glufster挂载失败Mount failed. Please check the log file for more details解决办法

    设置两台glusterfs服务器主机名分别为gfs1,gfs2 设置好glusterfs挂载不成功提示如下 Mount failed. Please check the log file for mo ...

  3. hive中的几个参数:元数据配置、仓库位置、打印表字段相关参数

    hive仓库位置由以下参数决定,默认位置/user/hive/warehouse: <property>         <name>hive.metastore.wareho ...

  4. day13: 迭代器和生成器

    1,思考所有可以被for循环的:list,tuple,set,dict,range,enumerate,f,str,差不多了,为何这些数据类型可以被for循环呢? 2,一个标准的装饰器函数 from ...

  5. Java 输入/输出——重定向标准输入/输出

    在System类中提供了如下三个重定向标准输入/输出方法. static void setErr​(PrintStream err) Reassigns the "standard" ...

  6. day2_抓包-抓包工具Charles

    1.Charles功能简单描述 1)定位问题,前端的.后端的问题 2)发出去的请求,请求头.请求体,返回的数据 3)拦截请求,修改请求 2.Charles抓包(Android手机) 1.要求手机得和你 ...

  7. Java之旅_高级教程_实例_数组

    摘自:http://www.runoob.com/java/java-examples.html 1.数组排序及元素查找 以下实例演示了如何使用sort()方法对Java数组进行排序,及如何使用 bi ...

  8. tensorflow入门笔记(四) tf.summary 模块

    模块内的函数: tf.summary.audio(name, tensor, sample_rate, max_outputs=3, collections=None, family=None) 输出 ...

  9. tensorflow入门笔记(二) 滑动平均模型

    tensorflow提供的tf.train.ExponentialMovingAverage 类利用指数衰减维持变量的滑动平均. 当训练模型的时候,保持训练参数的滑动平均是非常有益的.评估时使用取平均 ...

  10. 如何在安装node\npm\cnpm

    1.安装node.js node.js的官方地址为:https://nodejs.org/en/download/. 根据windows版本后,选择要下载的安装包,下载完毕,按照windows一般应用 ...