Background – Why Async Dispose?
The .NET Team recently added support for asynchronous disposal of objects, via a new IAsyncDisposable interface.
A lot of the examples you can find will use this to let you dispose of asynchronous streams, but it’s also useful for disposal of other objects that may potentially trigger I/O.
This allows you to write code like this (which works in .NET Core 3.0, with C# 8):
await in front of the
using statement? That will tell the using block to call
DisposeAsync on the
SqlConnection object when we exit the using block, instead of the regular
Dispose method, and
await on the result.
What’s the benefit of this over a typical using? Well, if the database connection needs to go over the network to reset the connection, it will return the thread to the thread pool to do other work, rather than blocking it while the Dispose takes place.
The great thing about .NET Core 3.0 is that for any services you have registered as Scoped (i.e. they only live for the duration of the current HTTP request) that implement
IAsyncDisposable will be disposed of asynchronously at the end of the request, giving valuable thread time back to processing actual requests.
I recently added the functionality in the Autofac Dependency Injection library to support calling
DisposeAsync on a lifetime scope, which in turn calls
DisposeAsync on all services in that scope that implement
IAsyncDisposable. This extends to if you use Autofac as your .NET Core Service Provider as well. The functionality will be released in Autofac 5.0.0, or you can check out the relevant GitHub PR now to see what changes went in.
I thought I’d use this blog post to help people write their own classes that implement IAsyncDisposable, since I couldn’t find a lot of documentation on it, and had to go digging into .NET Core code on GitHub to figure out the best approach.
Implement IDisposable As Well
IAsyncDisposable isn’t a replacement for
IDisposable, it’s an additional way to dispose.
Basically, if you implement
IAsyncDisposable in your class, you should probably implement
IDisposable too and perform the synchronous equivalent of your disposal operation.
This goes double for library authors, who are not in control of the code that creates the object that needs to be disposed.
There’s a couple of reasons for this:
- If you don’t have a regular Dispose method, code that doesn’t run in an
asynccontext will have to block on your
DisposeAsyncto make it sync, which kind of defies the point, and is unpleasant:
- If your class gets put in a container, and then the container is disposed synchronously, an exception will be thrown (this is the behaviour of Autofac and the default .NET Core DI system), because these containers will refuse to call
DisposeAsyncfrom inside a regular
Only Add IAsyncDisposable If You Need To
This one is pretty simple; you should only add IAsyncDisposable to your class if you or a derived class may allocate resources that also implement IAsyncDisposable.
Don’t do this:
SemaphoreSlim doesn’t implement
IAsyncDisposable, so all this does is use up another thread pool thread to run the Dispose.
If you are writing a base class that might have derived classes with resources that need disposing asynchronously, you may wish to introduce a virtual
DisposeAsync method if you also have a base
In this case, I would suggest making your default implementation call Dispose directly without awaiting and return synchronously:
Base classes can override the
DisposeAsync method if they have resources that can be disposed of asynchronously, otherwise they can just override
Only Dispose Once (Sync or Async)
It’s recommended practice to make Dispose re-entrant, and only dispose of its resources once. With the asynchronous disposal behaviour, this is still true, and importantly, you should only allow either Dispose or DisposeAsync to actually do the dispose.
So your classes should have this pattern:
The reason I set
isDisposed to true in the above example before awaiting is because setting it afterwards would make it possible for a caller to double-dispose, by not awaiting on DisposeAsync, then calling Dispose. It’s unlikely, but possible.
If the class may be used in a multi-threaded context, consider using
Interlocked methods to set
isDisposed, to make sure two threads don’t try disposing at the same time.
This bit is mostly targeted at library developers, who might be targeting netstandard versions.
While the language implementations for asynchronous disposal are only available in netstandard2.1, there is a package from Microsoft that provides the IAsyncDisposable interface and related types for netstandard2.0 and .NET 4.6.1, Microsoft.Bcl.AsyncInterfaces (as David Fowler kindly pointed out to me in the Autofac PR).
This allows you to add conditional references that mean your library can implement asynchronous disposal in versions prior to .NET Standard 2.1, by adding the following conditional package reference:
Consider Adding GC.SuppressFinalize to DisposeAsync
If your class has a finalizer (or a derived class may have one), then you may already be calling
GC.SuppressFinalize(this) in your Dispose method.
Because your DisposeAsync method is another Dispose method, it should also call
GC.SuppressFinalize so the GC doesn’t have to call your destructor later.
This is a more complete example that provides protected virtual methods for disposal, in line with the recommended IDisposable pattern:
Putting GC.SuppressFinalize in the DisposeAsync method will actually cause a violation of the CA1816 analyzer rule if you have the analyzers installed, that says GC.SuppressFinalize should only be called from Dispose. I’m hoping that the rule will get updated at some point, but for now you may need to suppress that rule for the DisposeAsync method.
IAsyncDisposable can be really handy if you have resources to dispose of that may use I/O in that disposal, but be careful using it, and only add it if you actually need to!