This article will show by example how to dispose of disposable objects when using the async/ await pattern. When using await, Microsoft recommends using the ConfigureAwait method as shown below,
await this._channel.Writer.WriteAsync(item, token).ConfigureAwait(false);
Using ConfigureAwait(false) avoids forcing the callback to be invoked on the original context or scheduler as Steven Toub explains in this article: ConfigureAwait FAQ - .NET Blog (microsoft.com). This can improve performance and avoids deadlocks. But it’s a bit tricky when creating the object using the using pattern since ConfigureAwait() returns ConfiguredAsyncDisposable.
How To Implement
Let’s change some code in my open-source project Spargine. The code below converts a string to a Brotli compressed string.
await using(var inputStream = new MemoryStream(Encoding.Unicode.GetBytes(input)))
{
await using(var outputStream = new MemoryStream())
{
await using(var brotliStream = new BrotliStream(outputStream, level))
{
await inputStream.CopyToAsync(brotliStream).ConfigureAwait(false);
await brotliStream.FlushAsync().ConfigureAwait(false);
return Convert.ToBase64String(outputStream.ToArray());
}
}
}
When I add ConfigureAwait(false) to the creation of MemoryStream, an error will appear,
The error appears since inputStream is now actually a ConfiguredAsyncDisposable type so CopyToAsync() is not available. To fix this, we need create the disposable type, before the using statement as shown below,
var inputStream = new MemoryStream(Encoding.Unicode.GetBytes(input));
await using(inputStream.ConfigureAwait(false))
{
var outputStream = new MemoryStream();
await using(outputStream.ConfigureAwait(false))
{
var brotliStream = new BrotliStream(outputStream, level);
await using(brotliStream.ConfigureAwait(false))
{
await inputStream.CopyToAsync(brotliStream).ConfigureAwait(false);
await brotliStream.FlushAsync().ConfigureAwait(false);
return Convert.ToBase64String(outputStream.ToArray());
}
}
}
Unfortunately, this code will then cause the following error,
CA2000: Call System.IDisposable.Dispose on object created by 'new MemoryStream()' before all references to it are out of scope
To fix it, add this attribute to the method,
[SuppressMessage("Microsoft.Build", "CS2000")]
I do wish that Microsoft solved this issue without this many code changes on our part. Maybe someday... fingers crossed.
Summary
Hidden dispose issues are more difficult to solve, like this one. I highly recommend adding the IDisposableAnalyzers NuGet package to all your projects to uncover disposable issues like this. I believe this should be part of .NET, not a package that has to be added since virtual memory issues are a big deal if your codebase has any and I can say most of the projects out there do!
Do you have any questions or comments? Please make them below.