1. 什么是IDisposable?
IDisposable
接口是一个用于约定可进行释放资源操作的接口,一个类实现该接口则意味着可以使用接口约定的方法Dispose
来释放资源。其定义如下:public interface IDisposable
{
void Dispose();
}
1.1 资源
1.2 为什么要手动释放资源
IDispose
接口,同样的,对于这类资源,最好也手动管理。IDisposable
接口来对资源释放做出约定——当程序员看到一个类实现IDisposable
接口时,就应该想到在使用完该类的实例后就应该调用其Dispose
方法来及时释放资源。IDispose
接口的类,在C#中你通常可以采用如下方式来释放资源:UnmanagedResource resource = /* ... */;
try
{
// 各种操作
}
finally
{
resource.Dispose();
}
(注:在finally中释放是为了确保即便运行时出错也可以顺利释放资源)
2:using
using (UnmanagedResource resource = /* ... */)
{
// 离开using的作用域后会自动调用resource的Dispose方法
}
// 或者如果不需要额外控制作用域的简写
using UnmanagedResource resource = /* ... */;
2. 如何实现IDisposable
2.1 不太完美的基本实现
IDisposable
很容易实现,毕竟它只有一个方法需要实现,并且看上去只要在方法里释放掉需要释放的资源即可:class UnmanagedResource : IDisposable
{
public void Dispose()
{
// 释放需要释放的资源
}
}
2.2 如果使用者忘记了调用Dispose方法释放资源
Disposable
接口的类的实例调用Dispose
方法,但是,出于各种原因,或许是他是一名新手,或许他受到老板的催促,或许他昨天没睡好等等,这些都可能导致他没有仔细检查自己的代码。永远不要假设你的代码会被一直正确地使用,总得留下些兜底的东西,提高健壮性——把你的用户当做一个做着布朗运动的白痴,哪怕他可能是个经验丰富的程序员,甚至你自己。
Dispose
方法释放资源,就留着让GC来调用释放。还好,C#允许你让GC来帮助你调用一些方法——通过终结器。~
。如下:class UnmanagedResource : IDisposable
{
// UnmanagedResource的终结器
~UnmanagedResource()
{
// 一些操作
}
}
class UnmanagedResource : IDisposable
{
public void Dispose()
{
// 释放需要释放的资源
}
~UnmanagedResource()
{
// 终结器调用Dispose释放资源
Dispose();
}
}
(ps:你或许会觉得终结器很像C++的析构函数,无论是声明方式还是作用(释放资源)上,但是终结器和析构函数有本质上差别,但这里不展开讨论)
2.3 手动调用了Dispose后,终结器再次调用Dispose
Dispose
方法后,并不表示你就告诉了GC不要再调用它的终结器,实际上,在你调用Dispose
方法后,GC还是会在某一时刻调用终结器,而由于我们在终结器里调用了Dispose
方法,这会导致Dispose
方法再次被调用——Double Free!Dispose
方法里检查这个字段的值,一旦发现已经释放则过就立刻返回。如下:class UnmanagedResource : IDisposable
{
public void Dispose()
{
// 如果已经释放过就立刻返回
if (_disposed)
{
return;
}
// 释放需要释放的资源
// 标记已释放
_disposed = true;
}
~UnmanagedResource()
{
Dispose();
}
// 用于标记是否已经释放的字段
private bool _disposed;
}
Dispose
重复调用是安全的。不过,要知道终结器是会影响性能的,因此为了性能考虑,我们还是希望在Dispose
方法调用后阻止终结器的执行(毕竟这时候已经不需要GC兜底了)。而要实现这一目标十分简单,只需要在Dipose
方法中使用GC.SuppressFinalize(this)
告诉GC不要调用终结器即可。如下:class UnmanagedResource : IDisposable
{
public void Dispose()
{
if (_disposed)
{
return;
}
// 释放需要释放的资源
_disposed = true;
// 告诉GC不要调用当前实例(this)的终结器
GC.SuppressFinalize(this);
}
~UnmanagedResource()
{
Dispose();
}
private bool _disposed;
}
Dispose
方法,就会“抑制”GC对终结器的调用;而让终结器调用Dispose
也不会产生什么问题。2.4 不是任何时候都需要释放所有资源
class UnmanagedResource : IDisposable
{
// 其他代码
private FileStream _fileStream;
}
FileStream
是一个实现了IDisposable
的类,也就是说,FileStream
也需要进行释放。UnmanagedResource
不仅要释放自己的非托管资源,还要释放FileStream
。你或许认为只需要在UnmanagedResource
的Dispose
方法中调用一下FileStream
的Dispose
方法就行。如下:class UnmanagedResource : IDisposable
{
// 其它代码
public void Dispose()
{
// 其他代码
_fileStream.Dispose();
// 其它代码
}
private FileStream _fileStream;
}
UnmanagedResource
的Dispose
方法是由终结器调用的会发生什么?FileStream
的终结器先被调用了,执行过了其Dispose
方法释放资源,随后UnmanagedResource
的终结器调用Dispose
方法时会再次调用FileStream
的Dispose
方法——Double Free, Again。Dispose
方法是由终结器调用的,就不应该手动释放那些本身就实现了终结器的托管资源——这些资源的终结器很可能先被执行。仅当手动调用Dispose
方法时才手动释放那些实现了终结器的托管资源。Dispose
方法,用一个参数来指示Dispose
是否释放托管资源。稍作调整,实现如下:class UnmanagedResource : IDisposable
{
// 其它代码
private void Dispose(bool disposing)
{
// 其他代码
if (disposing)
{
// 释放托管资源
_fileStream.Dispose();
}
// 释放非托管资源
// 其它代码
}
}
disposing
参数的Dispose(bool disposing)
方法,当disposing
为true
时,同时释放托管资源和非托管资源;当disposing
为false
时,仅释放托管资源。另外,为了不公开不必要的接口,将其声明为private
。Dispose
方法和终结器中按需调用Dispose(bool disposing)
方法即可。class UnmanagedResource : IDisposable
{
// 其它代码
public void Dispose()
{
// disposing=true,手动释放托管资源
Dispose(true);
GC.SuppressFinalize(this);
}
~UnmanagedResource()
{
// disposing=false,不释放托管资源,交给终结器释放
Dispose(false);
}
private void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (disposing)
{
// 释放托管资源
}
// 释放非托管资源
_disposed = true;
}
}
2.5 考虑一下子类的资源释放
UnmanagedResource
的子类:class HandleResource : UnmanagedResource
{
private HandlePtr _handlePtr;
}
HandleResource
有自己的资源HandlePtr
,显然如果只是简单继承UnmanagedResource
的话,UnmanagedResource
的Dispose
方法并不能释放HandleResource
的HandlePtr
。UnmanagedResource
的Dispose
方法声明为virtual
并在HandleResource
里覆写;或者在HandleResource
里使用new
重新实现Dispose
似乎都可以:// 使用多态
class UnmanagedResource : IDisposable
{
public virtual void Dispose() { /* ... */}
}
class HandleResource : UnmanagedResource
{
public override void Dispose() { /* ... */}
}
// 重新实现
class UnmanagedResource : IDisposable
{
public void Dispose() { /* ... */}
}
class HandleResource : UnmanagedResource
{
public new void Dispose() { /* ... */}
}
HandleResource
重复做那些在它的父类UnmanagedResource
做过的事——解决重复释放、定义终结器以及区分对待托管和非托管资源。这太不“继承了”——显然,有更好的实现方法。
UnmanagedResource
的的Dispose(bool disposing)
方法访问权限更改为protected
,并修饰为virtual
,以让子类访问/覆盖:class UnmanagedResource : IDisposable
{
protected virtual void Dispose(bool disposing) { /* ... */ }
}
Dispose(bool disposing)
来实现自己想要的释放功能:class HandleResource : UnmanagedResource
{
protected override void Dispose(bool disposing)
{
// 其他代码
base.Dispose(disposing);
}
}
Dispose(bool disposing)
是虚方法,因此父类UnmanagedResource
的终结器和Dispose
方法中对Dispose(bool disposing)
的调用会受多态的影响,调用到正确的释放方法,故子类可以不必再做那些重复工作。3. 异步释放——IAsyncDisposable
IAsyncDisposable
接口是IDisposable
接口的异步版,其设计于处理那些可能造成明显阻塞的清理操作,通过异步释放来避免阻塞。其定义如下:public interface IAsyncDisposable
{
ValueTask DisposeAsync();
}
IDisposable
接口定义基本一致,唯一的区别就是IAsyncDisposable
具有异步特征(返回Task
/ValueTask
,方法名带Async后缀)。IAsyncDisposable
不是IDisposable
的子集(即不包含IDisposable
)。不过,尽管IAsyncDisposable
不是IDisposable
的子集,但是通常的建议是,实现IAsyncDisposable
接口的时候应当同时实现IDisposable
接口——除非确实无法实现同步释放。3.1 使用
IAsyncDisposable
和IDisposable
在使用上基本相似,唯一的区别就是需要加入await
关键字。UnmanagedResource res = /* ... */;
try
{
// 一些操作
}
finally
{
// 此处添加await等待DisposeAsync操作
await res.DisposeAsync();
}
await using (UnmanagedResource res = /* ... */)
{
// 一些操作
}
// 或者如果不需要考虑作用域
await using UnmanagedResource res = /* ... */;
3.2 与Disposable重要的区别
3.2.1 终结器不需要异步方法
await
),因此在终结器中调用其DisposeAsync
方法是不太明智的。当然,直接调用而不等待在某些情况下或许可行,但无论如何都不建议这样做。DisposeAsync
,因此实现IAsyncDisposable
的时候反而不用像实现IDisposable
那样考虑终结器以及因为终结器带来的各种问题。当然,也正因为如此,所以实现IAsyncDisposable
接口的时候应当同时实现IDisposable
接口,否则如果程序员忘记了调用DisposeAsync
方法,就很可能造成资源泄露,因此还是需要一些兜底。3.2.2 释放什么资源
IAsyncDisposable
就是IDisposable
的异步版,只是一个异步释放托管/非托管资源,另一个同步释放。然而,真的需要IAsyncDisposable
清理非托管资源吗?考虑下面几个问题:await
”的异步释放方法。Marshal
类的各种Free方法也没有异步版——毕竟那压根没必要。IAsyncDisposable
基本不用考虑释放非托管资源。IAsyncDisposable
究竟释放什么资源?IAsyncDisposable
的托管资源。class UnmanagedResource : IAsyncDisposable
{
public async ValueTask DisposeAsync()
{
await _socket.DisposeAsync();
}
// 假设Socket类实现了IAsyncDisposable
private Socket _socket;
}
UnmanagedResource
包含了一个Socket
类型的字段,假设Socket
类实现了IAsyncDisposable
,那么你可以在UnmanagedResource
的DisposeAsync
中调用Socket的DisposeAsync
来释放它。IAsyncDisposable
接口,否则可以交给IDispose
实现,毕竟释放这些资源不需要异步操作。3.3 IAsyncDisposable的实现
3.3.1 简单实现
IAsyncDisposable
的实现。类似于IDisposable
接口的实现,它的基本结构如下:class UnamangedResource : IAsyncDisposable
{
public async ValueTask DisposeAsync()
{
// 清理(托管)资源
if (_socket != null)
{
await _socket.DisposeAsync();
_socket = null;
}
// 清理非托管资源(如果有的话)
}
private Socket _socket;
}
sealed
)类不需要考虑)。IDisposable
,我们将释放主体实现提取到一个protected virtual
方法中,该方法命名为DisposeAsyncCore
:class UnamangedResource : IAsyncDisposable
{
public async ValueTask DisposeAsync()
{
await DisposeAsyncCore();
}
protected virtual async ValueTask DisposeAsyncCore()
{
// 清理(托管)资源
if (_socket != null)
{
await _socket.DisposeAsync();
_socket = null;
}
// 清理非托管资源(如果有的话)
}
private Socket _socket;
}
DisposeAsyncCore
方法来释放父类的资源,同时只需要覆写DisposeAsyncCore
方法就可以实现自己的释放操作:class HandleResource : UnamangedResource
{
protected override async ValueTask DisposeAsyncCore()
{
// 释放子类资源
// 调用父类的DisposeAsyncCore释放父类资源
await base.DisposeAsyncCore();
}
}
3.3.3 同时实现IDiposable
IAsyncDisposable
也常常要实现IDisposable
,因此实际上文的简单实现是有点缺陷的。暂时先不考虑方法的实现细节,我们仅从方法的抽象上去考虑问题。class UnamangedResource : IDisposable, IAsyncDisposable
{
public async ValueTask DisposeAsync();
public void Dispose();
~UnamangedResource();
protected virtual async ValueTask DisposeAsyncCore();
protected void Dispose(bool disposing);
}
IDisposable
接口后,有两个问题需要考虑:DisposeAsyn
方法被调用后需要抑制终结器DisposeAsyncCore
不再需要清理非托管资源,这一工作交给Dispose(bool disposing)
即可。(前文说过绝大部分情况下非托管资源的清理考虑不上异步)IAsyncDisposable
的部分,IDisposable
的实现细节不变):class UnamangedResource : IDisposable, IAsyncDisposable
{
public async ValueTask DisposeAsync()
{
// 清理托管资源
await DisposeAsyncCore();
// disposing=false,只需要让其清理非托管资源
Dispose(false);
// 同样,抑制终结器
GC.SuppressFinalize(this);
}
public void Dispose();
~UnamangedResource();
protected virtual async ValueTask DisposeAsyncCore()
{
// 清理托管资源(异步和同步)
}
protected void Dispose(bool disposing);
}
4. 总结
4.1 代码总览
4.1.1 IDisposable实现
查看代码
class UnmanagedResource : IDisposable
{
// 对IDisposable接口的实现
public void Dispose()
{
// 调用Dispose(true),同时释放托管资源与非托管资源
Dispose(true);
// 让GC不要调用终结器
GC.SuppressFinalize(this);
}
// UnmanagedResource的终结器
~UnmanagedResource()
{
// 调用Dispose(false),仅释放非托管资源,托管资源交给GC处理
Dispose(false);
}
// 释放非托管资源,并可以选择性释放托管资源,且可以让子类覆写的Dispose(bool disposing)方法
protected virtual void Dispose(bool disposing)
{
// 防止重复释放
if (_disposed)
{
return;
}
// disposing指示是否是否托管资源
if (disposing)
{
// 释放托管资源
}
// 释放非托管资源
// 标记已释放
_disposed = true;
}
}
4.1.2 IAsyncDisposable实现
查看代码
class UnamangedResource : IAsyncDisposable
{
public async ValueTask DisposeAsync()
{
await DisposeAsyncCore();
}
protected virtual async ValueTask DisposeAsyncCore()
{
// 清理(托管)资源
if (_socket != null)
{
await _socket.DisposeAsync();
_socket = null;
}
// 清理非托管资源(如果有的话)
}
private Socket _socket;
}
4.1.3 IAsyncDisposable +IDisposable实现
查看代码
class UnmanagedResource : IDisposable, IAsyncDisposable
{
// 对IDisposable接口的实现
public void Dispose()
{
// 调用Dispose(true),同时释放托管资源与非托管资源
Dispose(true);
// 让GC不要调用终结器
GC.SuppressFinalize(this);
}
// 对IAsyncDisposable接口的实现
public async ValueTask DisposeAsync()
{
// 调用DisposeAsyncCore清理托管资源
await DisposeAsyncCore();
// 调用Dispose(false),仅释放非托管资源
Dispose(false);
// 让GC不要调用终结器
GC.SuppressFinalize(this);
}
// UnmanagedResource的终结器
~UnmanagedResource()
{
// 调用Dispose(false),仅释放非托管资源,托管资源交给GC处理
Dispose(false);
}
// 释放非托管资源,并可以选择性释放托管资源,且可以让子类覆写的Dispose(bool disposing)方法
protected virtual void Dispose(bool disposing)
{
// 防止重复释放
if (_disposed)
{
return;
}
// disposing指示是否是否托管资源
if (disposing)
{
// 释放托管资源
}
// 释放非托管资源
// 标记已释放
_disposed = true;
}
// 异步释放托管资源
protected virtual async ValueTask DisposeAsyncCore()
{
// 清理托管资源(同步和异步)
}
}
参考资料/更多资料:
【1】:IDisposable 接口
【2】:实现 Dispose 方法
【3】:IAsyncDisposable 接口文章来源:https://www.toymoban.com/news/detail-706349.html
【4】:实现 DisposeAsync 方法文章来源地址https://www.toymoban.com/news/detail-706349.html
到了这里,关于.NET C#基础(9):资源释放 - 需要介入的资源管理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!