Реализация 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();
    }
}

1 ответ

Пример для тех, кто все еще не решается реализовать оба:

      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 (Ninject, StructureMap и т. д.), генераторы кода, такие как RestSharp , или генераторы прокси, такие как Castle.Proxy , могут не поддерживатьIAsyncDisposable. Не удалось передать объектIDisposableпредставит трудно поймать ошибки в вашем приложении. Принимая во внимание, что если вы реализуете это, худшее, что может случиться, — это взаимоблокировка в блоке finally (если вы сделаете это с помощью sync-over-async).

В общем, лучше поддерживать обе операции , если вы планируете сделать его общедоступным API или у вас нет контроля над временем жизни вашего класса (например, в контейнерах DI или других широко известных оболочках).

Как

Существует полный пример Microsoft о том, как реализовать их оба в наследуемом классе (незапечатанный, как в вашем примере) - https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-disposeasync# реализация-оба-распоряжения-и-асинхронные-распоряжение-шаблоны

      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;
    }
}
Другие вопросы по тегам