前言

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

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

为什么要确保呢?

是这样子的,画一个图。

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

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

  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。在此肯定整理不完,后续另外一节补齐。

后一节,异步。

重学c#系列——非托管实例(五)的更多相关文章

  1. 重学c#系列——c# 托管和非托管资源(三)

    前言 c# 托管和非托管比较重要,因为这涉及到资源的释放. 现在只要在计算机上运行的,无论玩出什么花来,整个什么概念,逃不过输入数据修改数据输出数据(计算机本质),这里面有个数据的输入,那么我们的内存 ...

  2. 重学c#系列——c# 托管和非托管资源与代码相关(四)

    前言 这是续第三节. 概况垃圾回收与我们写代码的关系: 强引用和弱引用 针对共享 Web 承载优化 垃圾回收和性能 应用程序域资源监视 正文 强引用和弱引用 垃圾回收器不能回收仍在引用的对象的内存-- ...

  3. 重学c#系列——字典(十一)

    前言 重学c#系列继续更新,简单看一下字典的源码. 看源码主要是解释一下江湖中的两个传言: 字典foreach 顺序是字典添加的顺序 字典删除元素后,字典顺序将会改变 正文 那么就从实例化开始看起,这 ...

  4. 重学c#系列——异常续[异常注意事项](七)

    前言 对上节异常的补充,也可以说是异常使用的注意事项. 正文 减少try catch的使用 前面提及到,如果一个方法没有实现该方法的效果,那么就应该抛出异常. 如果有约定那么可以按照约定,如果约定有歧 ...

  5. 重学c#系列——对c#粗浅的认识(一)

    前言 什么是c#呢? 首先你是如何读c#的呢?c sharp?或者c 井? 官方读法是:see sharp. 有没有发现开发多年,然后感觉名字不对. tip:为个人重新整理,如学习还是看官网,c# 文 ...

  6. 重学c#系列——异常(六)

    前言 用户觉得异常是不好的,认为出现异常是写的人的问题. 这是不全面,错误的出现并不总是编写程序的人的原因,有时会因为应用程序的最终用户引发的动作或运行代码的环境而发生错误,比如你用android4去 ...

  7. 重学c#系列——盛派自定义异常源码分析(八)

    前言 接着异常七后,因为以前看过盛派这块代码,正好重新整理一下. 正文 BaseException 首先看下BaseException 类: 继承:public class BaseException ...

  8. 重学Golang系列(一): 深入理解 interface和reflect

    前言 interface(即接口),是Go语言中一个重要的概念和知识点,而功能强大的reflect正是基于interface.本文即是对Go语言中的interface和reflect基础概念和用法的一 ...

  9. 重学c#系列——c#运行原理(二)

    前言 c# 是怎么运行的呢?是否和java一样运行在像jvm的虚拟机上呢?其实差不多,但是更广泛. c# 运行环境不仅c#可以运行,符合.net framework 开发规范的都可以运行. c# 程序 ...

随机推荐

  1. 入门大数据---Hbase搭建

    环境介绍 tuge1 tuge2 tuge3 tuge4 NameNode NameNode DataNode DataNode ZooKeeper ZooKeeper ZooKeeper ZooKe ...

  2. 安装Centos 7 并且配置远程登录

    安装: 1.安装VMware fusion.https://www.vmware.com/cn/products/fusion/fusion-evaluation.html 2.下载centos 7 ...

  3. css3实现背景颜色渐变,文字颜色渐变,边框颜色渐变

    css3的渐变可以使用2个或者多个指定的颜色之间显示平稳的过渡的效果.这篇文章主要介绍下css3实现背景颜色渐变,文字颜色渐变,边框颜色渐变的方法,以便大家学习参考! 1.css背景颜色渐变 代码: ...

  4. css3 pointer-events(阻止hover、active、onclick等触发事件)

    pointer-events CSS 属性指定在什么情况下 (如果有) 某个特定的图形元素可以成为鼠标事件的 target. /* Keyword values */ pointer-events: ...

  5. 利用FlubuCore用C#来写持续集成和持续部署脚本

    前言 随着近些年微服务的流行,有越来越多的开发者和团队所采纳和使用,它的确提供了很多的优势也解决了很多的问题,但是我们也知道也并不是银弹,提供优势的同时它也给我们的开发人员和团队也带来了很多的挑战. ...

  6. fastjson到底做错了什么?为什么会被频繁爆出漏洞?

    GitHub 15.8k Star 的Java工程师成神之路,不来了解一下吗! GitHub 15.8k Star 的Java工程师成神之路,真的不来了解一下吗! GitHub 15.8k Star ...

  7. OldTrafford after 102 days

    THE RED GO MARCHING ON   One Team One Love Through the highs and the lows   One hundred and two long ...

  8. 实现 React Hooks

    实现 React Hooks UI 开发有两个问题: 展示复用 逻辑复用 展示复用目前基本使用组件化来解决,逻辑复用一直以来都没有特别好的解决方案.React 从一开始的 mixin ,到 高阶组件 ...

  9. SCOI 2016 萌萌哒

    SCOI 2016 萌萌哒 solution 有点线段树的味道,但是并不是用线段树来做,而是用到另外一个区间修改和查询的利器--ST表 我们可以将一个点拆成\(logN\)个点,分别代表从点\(i\) ...

  10. 「区间DP」「洛谷PP3146 」[USACO16OPEN]248 G

    [USACO16OPEN]248 G 题目: 题目描述 Bessie likes downloading games to play on her cell phone, even though sh ...