Reputation: 38477
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
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
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