zoukankan      html  css  js  c++  java
  • 重学c#系列——非托管实例(五)

    前言

    托管资源到是好,有垃圾回收资源可以帮忙,即使需要我们的一些小小的调试来优化,也是让人感到欣慰的。但是非托管资源就显得苍白无力了,需要程序员自己去设计回收,同样有设计的地方也就能体现出程序员的设计水平。

    托管类在封装对非托管资源的直接引用或者间接引用时,需要制定专门的规则,确保非托管资源在回收类的一个实例时释放。

    为什么要确保呢?

    是这样子的,画一个图。

    上图中托管中生成并引用非托管,一但非托管和托管中的引用断开(托管资源被回收),那么这个时候非托管资源还在,那么释放这个问题就有一丢丢困难。

    常见的有两种机制来自动释放非托管资源。

    1. 声明一个构析函数作为一个类的一个成员。

    2. 在类中实现System.IDisposable.

    好的,接下来就开始看例子吧。

    正文

    构析函数

    先从构析函数看起吧。

    class Resource
    {
    	~Resource()
    	{
               //释放资源
    	}
    }
    

    在IL中是这样子的。

    protected override void Finalize()
    {
    	try
    	{
               //构析函数写的
    	}
    	finally
    	{
    		base.Finalize();
    	}
    }
    

    简单介绍一下这个Finalize 是一个终结器,我们无法重写,文档中原文是这样子的。

    从包装非托管资源的 SafeHandle 派生的类(推荐),或对 Object.Finalize 方法的重写。 SafeHandle 类提供了终结器,因此你无需自行编写。
    

    这个SafeHandle 是啥呢?是安全句柄。这东西学问很大,非该文重点,先可以理解为句柄即可。

    这里简单介绍一下句柄。

    职业盗图:

    再次职业盗图:

    假设有一个句柄为0X00000AC6。有一个区域存储这各个对象的地址,0X00000AC6指向这个区域里面的区域A,A只是这个区中的一个。这个A指向真实的对象在内存中的位置。

    这时候就有疑问了,那么不是和指针一个样子吗?唯一不同的是指针的指针啊。是的,就是指针的指针。但是为啥要这么做呢?

    是这样子的,对象在内存中的位置是变化的,而不是不变的。我们有时候看到电脑下面冒红灯,这时候产生了虚拟内存,实际就是把硬盘当做内存了。但是我们发现电脑有点卡后,但是程序没有崩溃。

    当对象内存写入我们的硬盘,使用的时候又读出来了,这时候内存地址是变化了。这时候在内存中的操作是区域A的值变化了,而句柄的值没有变化,因为它指向区域A。

    现在我们通过实现构析函数来实现释放非托管资源,那么这种方式怎么样呢?这种方式是存在问题的,所以现在c#的构析函数去释放非托管谈的也不多。

    主要问题如下:

    1. 无法确认构析函数何时执行,垃圾回收机制不会马上回收这个对象,那么也就不会立即执行构析函数。

    2. 构析函数的实现会延迟该对象在内存中的存在时间。没有构析函数的对象,会在垃圾回收器中一次处理从内存中删除,实现构析函数的对象需要两次。

    然后所有对象的终结器是由一个线程来完成的,如果Finalize中存在复杂的业务操作,那么系统性能下降是可以预见的。

    实现IDisposable

    看例子:

    class Resource : IDisposable
    {
    	public void Dispose()
    	{
    	   //释放资源
    	}
    }
    

    然后只要用完调用Dispose即可。

    但是可能有时候程序员忘记主动调用了Dispose。

    所以改成这样。

    class Resource : IDisposable
    {
    	bool _isDisposed=false;
    	public void Dispose()
    	{
    		//释放资源
    		_isDisposed = true;
    		//标志不用掉析构函数
    		GC.SuppressFinalize(this);
    	}
    	~Resource()
    	{
    		if (_isDisposed)
    		{
    			return;
    		}
    		this.Dispose();
    	}
    }
    

    那么是否这样就结束了呢?

    不是的。

    文档中这样介绍道:任何非密封类都应具有要实现的附加 Dispose(bool) 重载方法。

    为什么这样说呢?因为是这样子的,不是密封类,那么可能会成为某个类的基类,那么子类就要考虑基类如何释放啊,所以加一个重载方法。

    注:从终结器调用时,disposing 参数应为 false,从 IDisposable.Dispose 方法调用时应为 true。 换言之,确定情况下调用时为 true,而在不确定情况下调用时为 false。
    
    class Resource : IDisposable
    {
    	bool _isDisposed=false;
    	public void Dispose()
    	{
    		//释放资源
    		Dispose(true);
    		//标志不用掉析构函数
    		GC.SuppressFinalize(this);
    	}
    	~Resource()
    	{
    		this.Dispose(false);
    	}
    	protected virtual void Dispose(bool disposing)
    	{
    		if (_isDisposed)
    		{
    			return;
    		}
    		if (disposing)
    		{
    			//释放托管相关资源
    		}
    		//释放非托管资源
    		_isDisposed = true;
    	}
    }
    

    看下思路:

    Dispose(bool) 方法重载

    方法的主体包含两个代码块:

    释放非托管资源的块。 无论 disposing 参数的值如何,都会执行此块。
    
    释放托管资源的条件块。 如果 disposing 的值为 true,则执行此块。 它释放的托管资源可包括:
    
    实现 IDisposable 的托管对象。 可用于调用其 Dispose 实现(级联释放)的条件块。 如果你已使用 System.Runtime.InteropServices.SafeHandle 的派生类来包装非托管资源,则应在此处调用 SafeHandle.Dispose() 实现。
    
    占用大量内存或使用短缺资源的托管对象。 将大型托管对象引用分配到 null,使它们更有可能无法访问。 相比以非确定性方式回收它们,这样做释放的速度更快。
    

    那么为什么明确去释放实现IDisposable 的托管资源呢?

    文档中回答是这样子的:

    如果你的类拥有一个字段或属性,并且其类型实现 IDisposable,则包含类本身还应实现 IDisposable。 实例化 IDisposable 实现并将其存储为实例成员的类,也负责清理。 这是为了帮助确保引用的可释放类型可通过 Dispose 方法明确执行清理。
    

    给个完整例子。

    class Resource : IDisposable
    {
    	bool _isDisposed=false;
    	private SafeHandle _safeHandle = new SafeFileHandle(IntPtr.Zero, true);
    	public void Dispose()
    	{
    		//释放资源
    		Dispose(true);
    		//标志不用掉析构函数
    		GC.SuppressFinalize(this);
    	}
    	~Resource()
    	{
    		this.Dispose(false);
    	}
    	protected virtual void Dispose(bool disposing)
    	{
    		if (_isDisposed)
    		{
    			return;
    		}
    		if (disposing)
    		{
    			_safeHandle?.Dispose();
    			//释放托管相关资源
    		}
    		//释放非托管资源
    		_isDisposed = true;
    	}
    }
    

    _safeHandle 和 Resource 一样同样可以通过构析函数去释放非托管,但是呢,如果自己Resource 主动Dispose去释放,那么最好把它的子对象(托管)的Dispose给执行了,好处上面写了。

    那么这时候为什么在构析函数中为显示为false呢?因为构析函数这时候本质是在终结器中执行,属于系统那一套,有太多不确定因素了,所以干脆_safeHandle 自己去调用自己析构函数。

    后来我发现.net core和.net framework,他们的构析函数执行方式是不一样的。

    举个栗子:

    static void Main(string[] args)
    {
    	{
    		Resource resource = new Resource();
    	}
    	GC.Collect();
    	Console.Read();
    }
    

    在.net framework 中马上回去调用构析函数,但是在.net core中并不会,等了几分钟没有反应。

    原因可以在:

    https://github.com/dotnet/corefx/issues/5205

    知道了大概怎么回事。

    好的,回到非托管中来。

    那么继承它的子类怎么写呢?

    class ResourceChild: Resource
    {
    	bool _isDisposed = false;
    	~ResourceChild()
    	{
    		Dispose(false);
    	}
    	protected override void Dispose(bool disposing)
    	{
    		if (_isDisposed)
    		{
    			return;
    		}
    		if (disposing)
    		{
    			//释放托管相关资源
    		}
    		//释放非托管资源
    		_isDisposed = true;
    		base.Dispose();
    	}
    }
    

    非托管有太多的东西了,比如说异步dispose,using。在此肯定整理不完,后续另外一节补齐。

    后一节,异步。

  • 相关阅读:
    Server Tomcat v8.0 Server at localhost was unable to start within 45 seconds. If the server requires more time, try increasing the timeout in the server editor.
    用户画像——“打标签”
    python replace函数替换无效问题
    python向mysql插入数据一直报TypeError: must be real number,not str
    《亿级用户下的新浪微博平台架构》读后感
    【2-10】标准 2 维表问题
    【2-8】集合划分问题(给定要分成几个集合)
    【2-7】集合划分问题
    【2-6】排列的字典序问题
    【2-5】有重复元素的排列问题
  • 原文地址:https://www.cnblogs.com/aoximin/p/13377264.html
Copyright © 2011-2022 走看看