Back to Blog
C#

c# await using: Asynchronous Resource Disposal

Learn how to use c# await using to dispose IAsyncDisposable resources asynchronously, with syntax, examples, and practical guidance.

IAsyncDisposableasync disposalresource managementC# usingasynchronous programming
Illustration of C# await using for asynchronous resource disposal with a shield and clock metaphor.

When a type implements IDisposable, the using statement guarantees that Dispose() is called when the block exits. But some resources, such as network connections, file streams, or database clients, require asynchronous cleanup to release them properly. C# 8.0 introduced await using to handle this scenario through the IAsyncDisposable interface. This article explains how c# await using works, how to implement it, and when it is the right choice.

What Problem Does await using Solve?

Synchronous Dispose() can block the calling thread when the cleanup operation involves I/O, such as flushing a buffer, closing a socket, or waiting for a remote service to acknowledge shutdown. In an asynchronous application, blocking the thread during cleanup defeats the purpose of using async I/O in the first place. IAsyncDisposable provides an asynchronous DisposeAsync() method that can perform cleanup without blocking. The await using statement ensures that DisposeAsync() is called and awaited when the scope exits.

The IAsyncDisposable Interface

The interface is defined in the System namespace and looks like this:

public interface IAsyncDisposable { ValueTask DisposeAsync(); }

The method returns ValueTask rather than Task to minimize allocation when disposal completes synchronously. A type that owns an asynchronous resource implements this interface and performs its cleanup inside DisposeAsync(). For example, a custom class that wraps an HttpClient or a database connection might need to close that resource asynchronously.

Here is a minimal implementation:

public class AsyncResource : IAsyncDisposable { private readonly HttpClient _httpClient; public AsyncResource() { _httpClient = new HttpClient(); } public async ValueTask DisposeAsync() { // Perform asynchronous cleanup await _httpClient.GetAsync("https://example.com/cleanup"); _httpClient.Dispose(); } }

This example is simplified; in practice, you would not call a network request during disposal. The point is that DisposeAsync() can contain any asynchronous logic.

Basic Syntax of await using

The syntax mirrors the traditional using statement but adds await before using:

await using (var resource = new AsyncResource()) { // Use the resource }

When the block exits, the compiler generates code that calls DisposeAsync() and awaits the returned ValueTask. The await using statement also works with the declaration form:

await using var resource = new AsyncResource(); // Use the resource // DisposeAsync is called when the variable goes out of scope

The declaration form is convenient when the resource should be disposed at the end of the current scope, such as the end of a method or a loop iteration.

How the Compiler Transforms await using

The compiler expands await using into a try/finally block, similar to the synchronous using, but with an asynchronous disposal call. The generated code checks whether the resource is null and then awaits DisposeAsync():

var resource = new AsyncResource(); try { // Use the resource } finally { if (resource != null) { await resource.DisposeAsync(); } }

This transformation ensures that disposal happens even if an exception is thrown inside the block. The await keyword in the finally block is allowed because the entire method is async. This is a key difference from the synchronous using, where Dispose() is called without awaiting.

Common Mistakes and Pitfalls

One common mistake is using await using with a type that only implements IDisposable but not IAsyncDisposable. The compiler will raise an error because the type cannot be used with await using. You must ensure the type implements IAsyncDisposable.

Another pitfall is mixing using and await using on the same resource. If a type implements both interfaces, the compiler will choose the synchronous Dispose() when you use using and the asynchronous DisposeAsync() when you use await using. This can lead to different cleanup behavior. Decide which one is appropriate for the resource and be consistent.

Also, be careful with await using in a loop. Each iteration creates a new scope, so the resource is disposed at the end of each iteration. If you need to reuse a resource across iterations, declare it outside the loop.

Performance and Runtime Considerations

await using introduces a small overhead compared to synchronous using because it involves awaiting a ValueTask. However, the overhead is negligible in most applications. The main performance benefit is that it prevents blocking the thread during cleanup, which can be significant when dealing with high-latency I/O.

The ValueTask returned by DisposeAsync() can be completed synchronously if the cleanup does not need to yield. In that case, awaiting it does not cause a thread switch or allocation. The compiler optimizes for this scenario.

One thing to keep in mind is that DisposeAsync() should not throw exceptions unless absolutely necessary. If it does, the exception will propagate from the await using block, potentially masking the original exception if one was thrown inside the block. This is similar to the behavior of synchronous Dispose().

When to Use await using vs using

Use await using when the type implements IAsyncDisposable and the cleanup involves asynchronous operations, such as closing a network stream, flushing a buffer to disk, or releasing a database connection. If the cleanup is purely in-memory and does not require I/O, the synchronous Dispose() is usually sufficient and avoids the extra await.

For types that implement both interfaces, you should choose the one that matches the resource's actual cleanup requirements. If the cleanup is inherently asynchronous, use await using. If it is synchronous, use using. Mixing them can lead to subtle bugs.

Advanced Usage: Implementing IAsyncDisposable with a Dispose Pattern

When a class holds both synchronous and asynchronous resources, you may need to implement both IDisposable and IAsyncDisposable. The recommended pattern is to have a single private method that performs the actual cleanup, and both Dispose() and DisposeAsync() call it. However, you must be careful about the order of disposal and the state of the object.

Here is an example that combines both interfaces:

public class MixedResource : IDisposable, IAsyncDisposable { private readonly HttpClient _httpClient; private readonly FileStream _fileStream; private bool _disposed; public MixedResource() { _httpClient = new HttpClient(); _fileStream = new FileStream("data.txt", FileMode.Open); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } public async ValueTask DisposeAsync() { await DisposeAsyncCore(); Dispose(false); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (_disposed) return; if (disposing) { _fileStream.Dispose(); } _disposed = true; } protected virtual async ValueTask DisposeAsyncCore() { if (_disposed) return; // Asynchronous cleanup await _httpClient.GetAsync("https://example.com/cleanup"); _httpClient.Dispose(); } }

This pattern ensures that both synchronous and asynchronous disposal paths are available, but it adds complexity. Only implement both interfaces when you genuinely need to support both usage patterns.

Compatibility and Language Version Requirements

await using requires C# 8.0 or later and the .NET Core 3.0 SDK or later. It is available in .NET 5, .NET 6, and newer versions. If you are targeting .NET Framework, you may need to use a compatibility package or rely on the synchronous using instead. The IAsyncDisposable interface is part of the .NET Standard 2.1, so it is available in libraries that target that standard.

c# await using: Asynchronous Resource Disposal | RYUSLOG DEV