我一直在熟悉C# 8和.NET Core 3.0中添加的一些东西(计划添加),并且不确定实现IAsyncDisposable 的正确方法(在撰写本文时,此链接实际上没有任何指导)。
特别是,我不清楚在未显式释放实例的情况下该怎么办 - 也就是说,它没有包装在async using(...)
中并且没有显式调用.DisposeAsync()
。
我的第一个想法是做我在实现 IDisposable 时要做的同样的事情:
- 我的
DisposeAsync()
实现调用具有disposing: true
的DisposeAsync(bool disposing)
- 实现一个终结器(带
~MyType()
),用于调用DisposeAsync(disposing: false)
DisposeAsync(bool disposing)
实际上释放和/或处置所有内容,并在disposing == true
时抑制最终确定。
我担心的是,没有什么可以等待终结器中DisposeAsync(bool)
的结果,并且在终结器中明确等待似乎真的很危险。
当然,"只是泄漏"似乎也不理想。
为了具体说明这一点,下面是一个(简化的)示例类,它确实有一个终结器:
internal sealed class TestAsyncReader: IAsyncDisposable
{
private bool IsDisposed => Inner == null;
private TextReader Inner;
internal TestAsyncReader(TextReader inner)
{
Inner = inner;
}
// the question is, should this exist?
~TestAsyncReader()
{
DisposeAsync(disposing: false);
}
private ValueTask DisposeAsync(bool disposing)
{
// double dispose is legal, but try and do nothing anyway
if (IsDisposed)
{
return default;
}
// should a finalizer even exist?
if (disposing)
{
GC.SuppressFinalize(this);
}
// in real code some resources explicitly implement IAsyncDisposable,
// but for illustration purposes this code has an interface test
if (Inner is IAsyncDisposable supportsAsync)
{
var ret = supportsAsync.DisposeAsync();
Inner = null;
return ret;
}
// dispose synchronously, which is uninteresting
Inner.Dispose();
Inner = null;
return default;
}
public ValueTask DisposeAsync()
=> DisposeAsync(disposing: true);
}
那么,是否有任何关于正确处理泄露IAsyncDisposable
实例的指导?
基于如何在 .NET Core 类中实现它的示例(如此处)以及那里的一些建议,我想说当你需要实现IAsyncDisposable
时,好的做法是同时实现IAsyncDisposable
和IDisposable
。在这种情况下,IAsyncDisposable
将只负责需要异步处理的显式场景,而IDisposable
应该根据一次性模式实践照常实现,它将服务于所有回退场景,包括那些事情最终确定的场景。因此,您不需要像DisposeAsync(bool disposing)
这样的东西 - 异步处置不能也不应该在终结器中发生。唯一的坏消息是,您必须支持资源回收的两种路径(同步和异步)。
Microsoft发布了自己关于这个问题的指南.
在接受的答案中,您应该实现两个接口
如果实现 IAsyncDisposable 接口,但不实现 IDisposable 接口,则应用可能会泄漏资源。如果一个类实现了 IAsyncDisposable,但不是 IDisposable,并且使用者只调用 Dispose,则您的实现永远不会调用 DisposeAsync。这将导致资源泄漏。
但您可能还需要实现 2 种释放模式:
using System;
using System.IO;
using System.Threading.Tasks;
class ExampleConjunctiveDisposableusing : IDisposable, IAsyncDisposable
{
IDisposable? _disposableResource = new MemoryStream();
IAsyncDisposable? _asyncDisposableResource = new MemoryStream();
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
public async ValueTask DisposeAsync()
{
await DisposeAsyncCore().ConfigureAwait(false);
Dispose(disposing: false);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_disposableResource?.Dispose();
(_asyncDisposableResource as IDisposable)?.Dispose();
_disposableResource = null;
_asyncDisposableResource = null;
}
}
protected virtual async ValueTask DisposeAsyncCore()
{
if (_asyncDisposableResource is not null)
{
await _asyncDisposableResource.DisposeAsync();
}
if (_disposableResource is IAsyncDisposable disposable)
{
await disposable.DisposeAsync();
}
else
{
_disposableResource?.Dispose();
}
_asyncDisposableResource = null;
_disposableResource = null;
}
}