假设我有一个非密封类,不处理任何非托管资源。我需要在其处置阶段进行一次异步调用来进行一些清理。没有其他托管资源需要处理。
据我了解,为了进行异步清理调用,我必须实现 IAsyncDisposable 并使用 DisposeAsync() 和 DisposeAsyncCore() 方法。但该指南指出,当您实现异步处置模式时,您还应该实现处置模式。这一切都很好,但在 Dispose() 中我实际上不需要做任何事情。
所以我的问题是,Dispose() 逻辑应该为空还是我需要一些东西以同步方式进行异步清理? (请参阅代码中有关“如果有什么东西应该放在这里怎么办”的注释)。
public class MyClass : IDisposable, IAsyncDisposable
{
private bool disposed;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public async ValueTask DisposeAsync()
{
await DisposeAsyncCore().ConfigureAwait(false);
Dispose(false);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
// What if anything should go here?
}
disposed = true;
}
}
protected virtual async ValueTask DisposeAsyncCore()
{
// Make async cleanup call here e.g. Database.CleanupAsync();
}
}
对于那些仍然犹豫是否要同时实施两者的人来说是一个例子:
internal class Program
{
static void Main(string[] args)
{
foreach (var a in new B()){}
//IAsyncDisposable is not called - you leaking resources.
//No deadlocks in UI, no warning in compilation, nothing.
//So it is better to be on safe side and implement both
//because you never know how one will manage lifetime of your class.
}
public class B : IEnumerable, IAsyncEnumerable<object>
{
public IEnumerator GetEnumerator() => new A();
public IAsyncEnumerator<object> GetAsyncEnumerator(CancellationToken ct) => new A();
}
public class A : IAsyncEnumerator<object>, IEnumerator
{
public ValueTask DisposeAsync()
{
Console.WriteLine("Async Disposed");
return ValueTask.CompletedTask;
}
public bool MoveNext() => false;
public void Reset(){}
public ValueTask<bool> MoveNextAsync() => ValueTask.FromResult(false);
public object Current => null;
}
}
结论
您可以仅自由添加对异步版本的支持,但要注意:一些包装,如
foreach
或旧版本的 DI 容器(Ninject、StructureMap 等)、代码生成器(如 RestSharp)或代理生成器(如Castle.Proxy 可能不支持 IAsyncDisposable
。未能将对象强制转换为 IDisposable
将会导致应用程序中难以捕获错误。然而,如果你确实实现了它,可能发生的最糟糕的事情是finally块中的死锁(如果你通过异步同步来实现)。
一般来说,如果您计划将其公开 API 或者您无法控制类的生命周期(例如在 DI 容器或其他众所周知的包装器中),最好支持这两种操作。
如何
有完整的 Microsoft 示例,介绍如何在可继承类中实现它们(非密封,如您的示例中) - https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/实现 disposeasync#implement-both-dispose-and-async-dispose-patterns
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);
#pragma warning disable CA1816 // Dispose methods should call SuppressFinalize
GC.SuppressFinalize(this);
#pragma warning restore CA1816 // Dispose methods should call SuppressFinalize
}
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().ConfigureAwait(false);
}
if (_disposableResource is IAsyncDisposable disposable)
{
await disposable.DisposeAsync().ConfigureAwait(false);
}
else
{
_disposableResource?.Dispose();
}
_asyncDisposableResource = null;
_disposableResource = null;
}
}
dispose 功能的两种实现都是从调用者的角度来看的。然后,您的类将提供两种机制来处置任何托管和非托管资源,并且调用者应用程序决定选择什么。这也确保了任何无法使用异步模式的消费者都不会丢失。
如果您确定或想要强制异步使用您的类,则实际上不需要实现同步处置。
因此,根据您对类使用的看法,您可以选择如何处置对象。如果您选择保留这两种机制,则可以以两种方式处置所有资源。
正如你所说,该课程是非密封的。对于密封类,实现
I(Async)Disposable
接口就足够了。 Disposable
模式的存在是因为派生类可能想要添加同步或异步的清理逻辑。你不可能知道。这就是为什么您需要为同步和异步情况实现整个模式。Dispose
方法中阻止异步调用。调用者有责任正确使用你的类。如果他决定调用 Dispose
而不是 DisposeAsync
并仅清除同步资源,这是他的决定/错误。如果 DisposeAsync
中的异步调用对于正确清理是绝对必要的,并且由您控制,请考虑添加在 Dispose
方法中使用的同步等效项。
给和我一样缺乏知识的人的注释......
我遇到了同样的问题,我只想实现 IAsyncDisposable 以在最后完成的
using
块内执行异步 HTTP 调用,但错误地使用了同步 using (await MyAsyncDisposable())
。解决方案是使用 await using (await MyAsyncDisposable())
,这样 IAsyncDisposable 就是唯一需要的。