Using Async/Await with Disposable Objects

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:

ScreenGrab

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.

If you liked this article, please buy David a cup of Coffee by going here: https://www.buymeacoffee.com/dotnetdave

Pick up any books by David McCarter by going to Amazon.com: http://bit.ly/RockYourCodeBooks

© The information in this article is copywritten and cannot be preproduced in any way without express permission from David McCarter.

3 thoughts on “Using Async/Await with Disposable Objects

  1. I use a lot of DataSet, DataTable and DataView in my project and calling dispose on those type are usually pointless and introduce many useless line in my code. IDisposableAnalyzers will report all my DataSet, DataTable and DataView variable and cause more problem for me … so basically I cannot use it even though I knew it is good. What do you think?

    1. My way of thinking is if a type implements IDisposable, it must be disposed of using the using statement. There is a reason it was written that way. I don’t have the time to investigate each type to see what Dispose actually does.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.