同时实现 IDisposable 和 IAsyncDisposable

mil*_*lie 5 c# dispose idisposable iasyncdisposable

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

据我了解,为了进行异步清理调用,我必须实现 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

eoc*_*ron 7

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

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

结论

您可以自由地仅添加对异步版本的支持,但要注意:某些包装,例如foreach旧版本的 DI 容器(NinjectStructureMap等)、代码生成器(例如RestSharp)或代理生成器(例如Castle.Proxy )可能不支持IAsyncDisposable。未能将对象转换为IDisposable将在您的应用程序中出现难以捕获的错误。然而,如果你确实实现了它,可能发生的最糟糕的事情是finally块中的死锁(如果你通过异步同步来实现)。

一般来说,如果您计划将其公开 API 或者您无法控制类的生命周期(例如在 DI 容器或其他众所周知的包装器中),那么最好支持这两种操作。

如何

有完整的微软示例,介绍如何在可继承类中实现它们(非密封,如您的示例) - https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing- 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;
    }
}
Run Code Online (Sandbox Code Playgroud)