如何处理泄漏的 IAsyncDisposable 实例是最佳建议?

12

我一直在熟悉即将添加到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 实例的指导吗?


2
这是添加了 IAsyncDisposableThreading.Timer 的提交链接:Here's the commit。虽然不知道是否有帮助,但这是它的实现示例... - Heretic Monkey
1
Dispose有两个作用:释放托管资源和释放非托管资源。我可以想象需要异步释放托管资源(例如需要优雅关闭的数据库连接),但我很难想到需要异步释放非托管资源(内存,文件指针等)。对于任何托管资源,它要么需要显式释放(例如事件订阅),要么最好释放但如果没有释放,它的终结器可以处理它--无论哪种方式,你的终结器都不应该介入其中。 - canton7
3
顺便提一下,尽量避免编写自己的终结器。这会使您的对象分配更加昂贵,并且很容易出错(例如,大多数人都不知道在调用本地方法时可以对其可终止对象进行终结,这会导致各种问题)。使用 SafeHandle 及其派生类更加容易和安全。这样可以规避您的问题。 - canton7
3
“Dispose(bool disposing)” 是一个可怕的模式。为了所有神圣的爱,让我们不要用同样的方式处理 “DisposeAsync”。 - Stephen Cleary
@StephenCleary 我们如何将 finalizers 和 DisposeAsync 集成? - bboyle1234
3
@bboyle1234: 我一直建议仅对包装非托管资源的类型使用终结器。这些类型应该只具有同步的Dispose方法。如果您遵循此模式,则永远不需要将终结器与DisposeAsync集成。 - Stephen Cleary
2个回答

12
基于.NET Core类(例如这里)中的实现示例和此处的一些建议,当需要实现IAsyncDisposable时,良好的实践是同时实现IAsyncDisposableIDisposable。在这种情况下,IAsyncDisposable仅负责显式场景,即需要异步处理时,而IDisposable则按照可处理模式的惯例实现,并将为包括终结操作在内的所有后备方案提供服务。因此,您不需要像DisposeAsync(bool disposing)一样,异步清理不能且不应该在终结器中发生。唯一的坏消息是您必须支持资源回收的两个路径(同步和异步)。

0

Microsoft发布了自己的指南,解决了这个问题。
就像被接受的答案一样,你应该实现两个接口

如果你只实现了IAsyncDisposable接口而没有实现IDisposable接口,你的应用程序可能会泄漏资源。如果一个类实现了IAsyncDisposable但没有实现IDisposable,而消费者只调用Dispose,则你的实现永远不会调用DisposeAsync。这将导致资源泄漏。

但是你也可能需要实现2个dispose模式:


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;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接