同时实现 IDisposable 和 IAsyncDisposable

问题描述 投票:0回答:4

假设我有一个非密封类,不处理任何非托管资源。我需要在其处置阶段进行一次异步调用来进行一些清理。没有其他托管资源需要处理。

据我了解,为了进行异步清理调用,我必须实现 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();
    }
}
c# dispose idisposable iasyncdisposable
4个回答
8
投票

对于那些仍然犹豫是否要同时实施两者的人来说是一个例子:

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 容器(NinjectStructureMap 等)、代码生成器(如 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;
    }
}

4
投票

dispose 功能的两种实现都是从调用者的角度来看的。然后,您的类将提供两种机制来处置任何托管和非托管资源,并且调用者应用程序决定选择什么。这也确保了任何无法使用异步模式的消费者都不会丢失。

如果您确定或想要强制异步使用您的类,则实际上不需要实现同步处置。

因此,根据您对类使用的看法,您可以选择如何处置对象。如果您选择保留这两种机制,则可以以两种方式处置所有资源。


1
投票

正如你所说,该课程是非密封的。对于密封类,实现

I(Async)Disposable
接口就足够了。
Disposable
模式的存在是因为派生类可能想要添加同步或异步的清理逻辑。你不可能知道。这就是为什么您需要为同步和异步情况实现整个模式。
对于你的问题。切勿在同步
Dispose
方法中阻止异步调用。调用者有责任正确使用你的类。如果他决定调用
Dispose
而不是
DisposeAsync
并仅清除同步资源,这是他的决定/错误。如果
DisposeAsync
中的异步调用对于正确清理是绝对必要的,并且由您控制,请考虑添加在
Dispose
方法中使用的同步等效项。


0
投票

给和我一样缺乏知识的人的注释......

我遇到了同样的问题,我只想实现 IAsyncDisposable 以在最后完成的

using
块内执行异步 HTTP 调用,但错误地使用了同步
using (await MyAsyncDisposable())
。解决方案是使用
await using (await MyAsyncDisposable())
,这样 IAsyncDisposable 就是唯一需要的。

© www.soinside.com 2019 - 2024. All rights reserved.