It’s no secret that JetBrains Rider is my IDE of choice. The JetBrains team’s ability to stay current with language additions has helped me keep my C# knowledge relevant in a fast-paced world. It’s always exciting to see refactoring recommendations, and while it’s easy to hit Alt+Enter
, I like to understand why I’m about to apply a change.
In this post, we’ll be looking at a relatively new keyword combination of await using
and what it means for our codebase.
IDisposable
The IDisposable
interface has been around since .NET Framework 1.0 and gives us the ability to release internal resources. Experienced .NET Developers are likely familiar with the disposing pattern.
using (var disposable = new Disposable()) {
// do your thing here...
}
This interface is fundamental to .NET, with the documentation page for IDisposable
listing over 500 implementations of the interface in the framework alone. The count doesn’t even include the use of IDisposable
in the open-source ecosystem and commercial products.
There is a flaw with IDisposable
, and it’s become more apparent with the introduction of Task
. The freeing of resources is synchronous and can block threads. Even worse, incorrect disposable of an asynchronous resource can lead to dead-locks.
IAsyncDisposable
In .NET Core 3.0, we see the introduction of an IAsyncDisposable
interface, which is currently derived by a handful of concepts as of writing this post: Microsoft.Extensions.DependencyInjection.ServiceProvider
, System.Collections.Generic.IAsyncEnumerator<T>
, System.IO.Stream
, System.Threading.Timer
, and System.Text.Json.Utf8JsonWriter
. The interface allows us to free up asynchronous resources by implementing a DisposeAsync
method that returns a ValueTask
.
public interface IAsyncDisposable
{
ValueTask DisposeAsync();
}
Note, that IDisposable
and IAsyncDisposable
are two independent interfaces and can be implemented exclusively of each other. We can imagine that IAsyncDisposable
does not implement IDisposable
because it does not want to give developers the false impression that there is a synchronous option by default in all cases.
Using IAsyncDisposable In Our Code
Let’s write a simple example that will allow C# to use the await using
construct. Our first step is to create a class that implements the IAsyncDisposable
interface.
public class HotGarbage : IAsyncDisposable
{
public async ValueTask DisposeAsync()
{
Console.WriteLine("What's that smell? *sniff sniff*");
await Task.Delay(TimeSpan.FromSeconds(3));
Console.WriteLine("Disposing of Hot Garbage");
}
public void Look()
{
Console.WriteLine("Hmmm...");
}
}
Again, we should take note that our class only implements IAsyncDisposable
, which means our class likely has dependencies that require asynchronous disposal.
Let’s use our new class.
class Program
{
static async Task Main(string[] args)
{
async Task Run()
{
await using var hotGarbage = new HotGarbage();
hotGarbage.Look();
}
await Run();
}
}
We are using a local function to create a scope that ends at the end of our code block. The await using
keywords are useful as we can reduce the number of braces in our code. Additionally, it reduces the likelihood that a refactor may introduce issues of incorrect disposal.
Running the code, we see the results of our asynchronous disposal.
Hmmm...
What's that smell? *sniff sniff*
Disposing of Hot Garbage
Conclusion
While our IDEs can make great suggestions, it is up to us to understand and implement those suggestions. The IAsyncDisposable
interface should make it easier for folks writing libraries that deal with I/O operations. Authors of database access libraries, image processing, and internet protocols should be able to dispose of managed resources gracefully without fear of dead-locks. For us consumers of libraries, we now get some sweet syntactic sugar in the await using
keyword, further reducing our codebase’s visual footprint.
What do you think? Do you utilize the await using
keywords combo in your code? Why or Why not? Please leave a comment below.