Muhammad Rehan Saeed
Muhammad Rehan Saeed

Reputation: 38477

Does IAsyncDisposable Have a Reference Implementation?

With IDisposable, I often used an abstract base class because implementing it was so difficult. Mostly because you can use the interface to dispose both managed and unmanaged references and each is disposed differently.

public abstract class Disposable : IDisposable
{
    ~Disposable() => this.Dispose(false);

    public bool IsDisposed { get; private set; }

    public void Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void DisposeManaged() {}
    protected virtual void DisposeUnmanaged() {}

    protected void ThrowIfDisposed()
    {
        if (this.IsDisposed)
        {
            throw new ObjectDisposedException(this.GetType().Name);
        }
    }

    private void Dispose(bool disposing)
    {
        if (!this.IsDisposed)
        {
            if (disposing)
            {
                this.DisposeManaged();
            }

            this.DisposeUnmanaged();
            this.IsDisposed = true;
        }
    }
}

With IAsyncDisposable, I can't find a reference implementation. Also, I believe it only handles managed resources mainly because it's so new and there are no unmanaged resources that require async to be disposed. Is this correct? If so, is the implementation then trivial?

Upvotes: 7

Views: 2645

Answers (2)

SmallSoft
SmallSoft

Reputation: 728

Update: .NET Documentation now has an article Implement a DisposeAsync method that describes recommended IAsyncDisposable implementation for non-sealed class that needs to support both synchronous and asynchronous disposal. It recommends adding separate virtual DisposeAsyncCore method:

using System;
using System.Text.Json;
using System.Threading.Tasks;

public class ExampleAsyncDisposable : IAsyncDisposable, IDisposable
{
    // To detect redundant calls
    private bool _disposed = false;

    // Created in .ctor, omitted for brevity.
    private Utf8JsonWriter _jsonWriter;

    public async ValueTask DisposeAsync()
    {
        await DisposeAsyncCore();

        Dispose(false);
        GC.SuppressFinalize(this);
    }

    protected virtual async ValueTask DisposeAsyncCore()
    {
        // Cascade async dispose calls
        if (_jsonWriter != null)
        {
            await _jsonWriter.DisposeAsync();
            _jsonWriter = null;
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed)
        {
            return;
        }

        if (disposing)
        {
            _jsonWriter?.Dispose();
            // TODO: dispose managed state (managed objects).
        }

        // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
        // TODO: set large fields to null.

        _disposed = true;
    }
}

The example assumes that only indirectly owned unmanaged resources can be disposed asynchronously. Directly owned managed resources are always disposed synchronously in protected virtual void Dispose method.


Old answer: The DisposeAsync method, unlike regular Dispose, should not be called from finalizer. Finalizers already run in dedicated thread and don't block anything, so there's no need. Therefore, DisposeAsync can always dispose both unmanaged and managed resources, and you don't need to implement a separate DisposeAsync(bool disposing) method.

The example of IAsyncDisposable implementation can be found in the sources of the new .NET Core type, Utf8JsonWriter (it is a sealed class and therefore don't use virtual method):

public async ValueTask DisposeAsync()
{
    if (_stream == null)
    {
        // The conditions are ordered with stream first as that would be the most common mode
        if (_output == null)
        {
            return;
        }
    }

    await FlushAsync().ConfigureAwait(false);
    ResetHelper();

    _stream = null;
    _arrayBufferWriter = null;
    _output = null;
}

Basically, it should do the same things as Dispose(true) in regular Disposable pattern, but asynchronously, if possible. If it's not possible to do disposal asynchronously, but you still need to implement this interface for some reason, you can fall back to synchronous disposal and return completed ValueTask after that, as done in System.IO.Stream.

Upvotes: 5

bartonjs
bartonjs

Reputation: 33168

The pattern that we decided on for the framework, and will publish in Framework Design Guidelines, 3rd Edition is:

public async ValueTask DisposeAsync()
{
    await DisposeAsyncCore().ConfigureAwait(false);
    Dispose(false);
    GC.SuppressFinalize(this);
}

protected virtual ValueTask DisposeAsyncCore()
{
    // Code goes here that is the async equivalent to Dispose(true)
}

That does, of course, imply that all IAsyncDisposable types are also IDisposable. Also, sealed types can skip DisposeAsyncCore().

Upvotes: 4

Related Questions