熟悉而陌生的新朋友——IAsyncDisposable

Posted 码睿鸭

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了熟悉而陌生的新朋友——IAsyncDisposable相关的知识,希望对你有一定的参考价值。

本文作者——句幽

.NET Core 3.0的版本更新中,官方我们带来了一个新的接口 IAsyncDisposable

小伙伴一看肯定就知道,它和.NET中原有的IDisposable接口肯定有着密不可分分的关系,且一定是它的异步实现版本。

那么.NET是为什么要在 .NET Core 3.0 (伴随C# 8) 发布的同时,带来该接口呢? 还有就是该异步版本和原来的IDispose有着什么样的区别呢? 到底在哪种场景下我们能使用它呢?

带着这些问题,我们今天一起来认识一下这位"新朋友" —— IAsyncDisposable

为了更好的了解它,让我们先来回顾一下.NET中的资源释放:

.NET的资源释放

由于.NET强大的GC,对于托管资源来说(比如C#的类实例),它的释放往往不需要开发人员来操心。

但是在开发过程中,有时候我们需要涉及到非托管的资源,比如I/O操作,将缓冲区中的文本内容保存到文件中、网络通讯,发送数据包等等。

由于这些操作GC没有办法控制,所以也就没有办法来管理它们的生命周期。如果使用了非托管资源之后,没有及时进行释放资源,那么就会造成内存的泄漏问题。

而.NET为我们提供了一些手段来进行资源释放的操作:

析构函数

析构函数在C#中是一个语法糖,在构造函数前方加一个符号即代表使用析构函数 。

public class ExampleClass
{
	public ExampleClass()
	{
	}

	~ExampleClass()	// 析构函数
	{
		// 释放非托管资源
	}
}

当一个类申明了析构函数了之后,GC将会对它进行特殊的处理,当该实例的资源被GC回收之前会调用析构函数。(该部分内容本文将不做过多介绍)

虽然析构函数方法在某些需要进行清理的情况下是有效的,但它有下面两个严重的缺点:

  • 只有在GC检测到某个对象可以被回收时才会调用该对象的终结方法,这发生在不再需要资源之后的某个不确定的时间。这样一来,开发人员可以或希望释放资源的时刻与资源实际被终结方法释放的时刻之间会有一个延迟。如果程序需要使用许多稀缺资源(容易耗尽的资源)或不释放资源的代价会很高(例如,大块的非托管内存),那么这样的延迟可能会让人无法接受。
  • 当CLR需要调用终结方法时,它必须把回收对象内存的工作推迟到垃圾收集的下一轮(终结方法会在两轮垃圾收集之间运行)。这意味着对象的内存会在很长一段时间内得不到释放。

因此,如果需要尽快回收非托管资源,或者资源很稀缺,或者对性能要求极高以至于无法接受在GC时增加额外开销,那么在这些情况下完全依靠析构函数的方法可能不太合适。

而框架提供了IDisposable接口,该接口为开发人员提供了一种手动释放非托管资源的方法,可以用来立即释放不再需要的非托管资源。

IDisposable

.NET Framework 1.1开始 ,.NET就为我们提供了IDispose接口。

使用该接口,我们可以实现名为Dispose的方法,进行一些手动释放资源的操作(包括托管资源和非托管资源)。

public class ExampleClass:IDisposable
{
	private Stream _memoryStream = new MemoryStream();

	public ExampleClass()
	{
	}
	
	public void Dispose()
	{
		// 释放资源
		myList.Clear();
		myData = null;
		_memoryStream.Dispose();	
	}
}

在C#中,我们除了可以手动调用 xx.Dispose()方法来触发释放之外,还可以使用using的语法糖。

当我们在 visual studio 中添加IDisposable接口时,它会提示我们使用是否使用“释放模式”:

“释放模式”所生成的代码如下:

protected virtual void Dispose(bool disposing)
{
	if (!disposedValue)
	{
		if (disposing)
		{
			// TODO: 释放托管状态(托管对象)
		}

		// TODO: 释放未托管的资源(未托管的对象)并重写终结器
		// TODO: 将大型字段设置为 null
		disposedValue = true;
	}
}

// // TODO: 仅当“Dispose(bool disposing)”拥有用于释放未托管资源的代码时才替代终结器
// ~ExampleClass()
// {
//     // 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中
//     Dispose(disposing: false);
// }

public void Dispose()
{
	// 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中
	Dispose(disposing: true);
	GC.SuppressFinalize(this);
}

释放资源的代码被放置在 Dispose(bool disposing) 方法中,你可以选用 析构函数 或者 IDisposable 来进行调用该方法。

这里说一下:在 IDisposable 的实现中,有一句 GC.SuppressFinalize(this);。 这句话的意思是,告诉GC,不需要对该类的析构函数进行单独处理了。也就是说,该类的析构函数将不会被调用。因为资源已经在 Dispose() 中被我清理了。

异步时代

.NET Core开始,就意味着.NET来到了一个全新的异步时代。无论是各种基础类库(比如System.IO)、AspNet Core、还是EFCore..... 它们都支持异步操作,应该说是推荐异步操作。

在今天,假如一个新项目没有使用 awaitasync。你都会觉得自己在写假代码

以上是关于熟悉而陌生的新朋友——IAsyncDisposable的主要内容,如果未能解决你的问题,请参考以下文章

那些你熟悉而又陌生的函数

熟悉而陌生——那些个系统抽象

熟悉而陌生——那些个系统抽象

熟悉而陌生——那些个系统抽象

一座熟悉而陌生的城市--一个程序员的成长史

Android:全面解析 熟悉而陌生 的Application类使用