重学c#系列——非托管实例(五)
前言
托管资源到是好,有垃圾回收资源可以帮忙,即使需要我们的一些小小的调试来优化,也是让人感到欣慰的。但是非托管资源就显得苍白无力了,需要程序员自己去设计回收,同样有设计的地方也就能体现出程序员的设计水平。
托管类在封装对非托管资源的直接引用或者间接引用时,需要制定专门的规则,确保非托管资源在回收类的一个实例时释放。
为什么要确保呢?
是这样子的,画一个图。
上图中托管中生成并引用非托管,一但非托管和托管中的引用断开(托管资源被回收),那么这个时候非托管资源还在,那么释放这个问题就有一丢丢困难。
常见的有两种机制来自动释放非托管资源。
声明一个构析函数作为一个类的一个成员。
在类中实现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#的构析函数去释放非托管谈的也不多。
主要问题如下:
无法确认构析函数何时执行,垃圾回收机制不会马上回收这个对象,那么也就不会立即执行构析函数。
构析函数的实现会延迟该对象在内存中的存在时间。没有构析函数的对象,会在垃圾回收器中一次处理从内存中删除,实现构析函数的对象需要两次。
然后所有对象的终结器是由一个线程来完成的,如果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#系列——非托管实例(五)的更多相关文章
- 重学c#系列——c# 托管和非托管资源(三)
前言 c# 托管和非托管比较重要,因为这涉及到资源的释放. 现在只要在计算机上运行的,无论玩出什么花来,整个什么概念,逃不过输入数据修改数据输出数据(计算机本质),这里面有个数据的输入,那么我们的内存 ...
- 重学c#系列——c# 托管和非托管资源与代码相关(四)
前言 这是续第三节. 概况垃圾回收与我们写代码的关系: 强引用和弱引用 针对共享 Web 承载优化 垃圾回收和性能 应用程序域资源监视 正文 强引用和弱引用 垃圾回收器不能回收仍在引用的对象的内存-- ...
- 重学c#系列——字典(十一)
前言 重学c#系列继续更新,简单看一下字典的源码. 看源码主要是解释一下江湖中的两个传言: 字典foreach 顺序是字典添加的顺序 字典删除元素后,字典顺序将会改变 正文 那么就从实例化开始看起,这 ...
- 重学c#系列——异常续[异常注意事项](七)
前言 对上节异常的补充,也可以说是异常使用的注意事项. 正文 减少try catch的使用 前面提及到,如果一个方法没有实现该方法的效果,那么就应该抛出异常. 如果有约定那么可以按照约定,如果约定有歧 ...
- 重学c#系列——对c#粗浅的认识(一)
前言 什么是c#呢? 首先你是如何读c#的呢?c sharp?或者c 井? 官方读法是:see sharp. 有没有发现开发多年,然后感觉名字不对. tip:为个人重新整理,如学习还是看官网,c# 文 ...
- 重学c#系列——异常(六)
前言 用户觉得异常是不好的,认为出现异常是写的人的问题. 这是不全面,错误的出现并不总是编写程序的人的原因,有时会因为应用程序的最终用户引发的动作或运行代码的环境而发生错误,比如你用android4去 ...
- 重学c#系列——盛派自定义异常源码分析(八)
前言 接着异常七后,因为以前看过盛派这块代码,正好重新整理一下. 正文 BaseException 首先看下BaseException 类: 继承:public class BaseException ...
- 重学Golang系列(一): 深入理解 interface和reflect
前言 interface(即接口),是Go语言中一个重要的概念和知识点,而功能强大的reflect正是基于interface.本文即是对Go语言中的interface和reflect基础概念和用法的一 ...
- 重学c#系列——c#运行原理(二)
前言 c# 是怎么运行的呢?是否和java一样运行在像jvm的虚拟机上呢?其实差不多,但是更广泛. c# 运行环境不仅c#可以运行,符合.net framework 开发规范的都可以运行. c# 程序 ...
随机推荐
- web安全之跨站脚本漏洞(XSS)
XSS(跨站脚本)概述以及pikachu上的实验操作 Cross-Site Scripting 简称为“CSS”,为避免与前端叠成样式表的缩写"CSS"冲突,故又称XSS. XSS ...
- maven跳过测试打包
1.在执行run as时候加上参数: clean install compile -Dmaven.test.skip=true 2.在pom文件中添加如下: <plugins> < ...
- 音视频前沿:新一代 AV1 视频标准究竟是怎样一种存在?
AV1是开放媒体联盟Alliance for Open Media (AOM) 开发的第一代视频编码标准,自推出以来获得了产业界巨大关注和支持.腾讯多媒体实验室也加入进来和其他公司团队一同积极推动AV ...
- lodash - slice
稀疏数组和密集数组 稀疏数组 Sparse arrays 一般来说,JavaScript 中的数组都是稀疏数组-它们可以拥有空槽,所谓空槽,指的就是数组的某个位置没有任何值,既不是 undefined ...
- tomcat结合shiro无文件webshell的技术研究以及检测方法
0x01简介 shiro结合tomcat回显,使用公开的方法,回显大多都会报错.因为生成的payload过大,而tomcat在默认情况下,接收的最大http头部大小为8192.如果超过这个大小,则to ...
- Java实现 第十一届蓝桥杯——走方格(渴望有题目的大佬能给小编提供一下题目,讨论群:99979568)
走方格 问题描述在平面上有一些二维的点阵. 这些点的编号就像二维数组的编号一样,从上到下依次为第 1 至第 n 行,从左到右依次为第1 至第 m 列,每一个点可以用行号和列号来表示. 现在有个人站在第 ...
- 一文说清 KubeSphere 容器平台的价值
KubeSphere 作为云原生家族 后起之秀,开源近两年的时间以来收获了诸多用户与开发者的认可.本文通过大白话从零诠释 KubeSphere 的定位与价值,以及不同团队为什么会选择 KubeSphe ...
- Linux下对拍(A+B问题)
对拍代码 #include<bits/stdc++.h> using namespace std; int main(){ for(int i=1;;i++){ system(" ...
- day48 work
1 navicat自己玩一玩 2 练习题一定要搞懂 照着我的思路一遍遍的看敲 3 熟悉pymysql的使用 4 sql注入产生的原因和解决方法 了解 5 思考:如何结合mysql实现用户的注册和登录功 ...
- Vue---day05
目录 2. 客户端项目搭建 2.1 创建项目目录 2.2 初始化项目 2.3 安装路由vue-router 2.3.1 下载安装路由组件 2.3.2 配置路由 2.3.2.1 初始化路由对象 2.3. ...