What's the recommended way to deal with leaked IAsyncDisposable instances?

夙愿已清 提交于 2021-02-18 22:31:31

问题


I've been familiarizing myself with some of the things (that are planned to be) added in C# 8 & .NET Core 3.0, and am unsure on the correct way to implement IAsyncDisposable (at time of writing, this link has literally no guidance).

In particular, it is unclear to me what to do in the case when an instance isn't explicitly disposed - that is, it isn't wrapped in an async using(...) and .DisposeAsync() isn't explicitly called.

My first thought was to do that same thing I'd do when implementing IDisposable:

  • My DisposeAsync() implementation calls a DisposeAsync(bool disposing) with disposing: true
  • Implement a finalizer (with ~MyType()) that calls DisposeAsync(disposing: false)
  • DisposeAsync(bool disposing) actually frees and/or disposes everything, and suppresses finalizing if disposing == true.

My concerns are that there's nothing to await the results of DisposeAsync(bool) in the finalizer, and explicitly waiting in a finalizer seems really really dangerous.

Of course "just leak" also seems less than ideal.

To make this concrete, here's a (simplified) example class that does have a finalizer:

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

So, is there any guidance around proper handling of leaked IAsyncDisposable instances?


回答1:


Basing on examples of how it's implemented inside .NET Core classes (like here) and some recommendations from there, I'd say that when you need to implement IAsyncDisposable, the good practice would be to implement both IAsyncDisposable and IDisposable. In this case IAsyncDisposable will be only responsible for explicit scenarios when asyncronus disposal is needed, while IDisposable is supposed to be implemented as usual according to disposable pattern practices and it's going to serve all fallback scenarios including those when things come to finalization. Thus you don't need to have anything like DisposeAsync(bool disposing) - the asynchronous disposal cannot and shouldn't happen in a finalizer. The only bad news that you'll have to support both paths for resources reclaiming (synchronous and asynchronous).



来源:https://stackoverflow.com/questions/55677445/whats-the-recommended-way-to-deal-with-leaked-iasyncdisposable-instances

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!