Каков рекомендуемый способ устранения утечек экземпляров IAsyncDisposable? - PullRequest
4 голосов
/ 14 апреля 2019

Я ознакомился с некоторыми вещами (которые планируется сделать), добавленными в C # 8 и .NET Core 3.0, и не уверен в правильном способе реализации IAsyncDisposable (в то время написания, эта ссылка буквально не имеет никаких указаний).

В частности, мне неясно, что делать в случае, когда экземпляр явно не расположен, то есть он не заключен в async using(...) и .DisposeAsync() явно не вызывается.

Моей первой мыслью было сделать то же самое, что я делал бы при реализации IDisposable :

  • Моя DisposeAsync() реализация вызывает DisposeAsync(bool disposing) с disposing: true
  • Реализация финализатора (с ~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 экземпляров?

1 Ответ

2 голосов
/ 14 апреля 2019

Основываясь на примерах того, как это реализовано в классах .NET Core (например, здесь ) и некоторых рекомендациях там , я бы сказал, что когда вам нужно реализовать IAsyncDisposable Хорошей практикой будет реализация как IAsyncDisposable, так и IDisposable. В этом случае IAsyncDisposable будет отвечать только за явные сценарии, когда необходимо удаление асинхронного, а IDisposable должен быть реализован как обычно в соответствии с одноразовыми шаблонами, и он будет обслуживать все запасные сценарии, включая те, когда дело доходит до завершения , Таким образом, вам не нужно иметь ничего похожего на DisposeAsync(bool disposing) - асинхронное удаление не может и не должно происходить в финализаторе. Единственная плохая новость, что вам придется поддерживать оба пути восстановления ресурсов (синхронный и асинхронный).

...