C#
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C#
-
Section 4: Working with Data
-
Section 5: Error Handling
-
Section 6: Delegates and Events
-
Section 7: Async Programming
-
Section 8: More Language Features
-
Section 9: File I/O and Serialization
-
Section 10: Networking in .NET
-
Section 11: The .NET Ecosystem
-
Section 12: Memory and Performance
-
Section 13: Concurrency Beyond Async
-
Section 14: Reflection and Attributes
-
Section 15: Testing and Best Practices
-
Section 16: Design Patterns in C#
-
Section 17: Standard Library Deep Dive
-
Section 18: Data Structures and Algorithms in C#
-
Section 19: GUI and Desktop Development Overview
-
Section 20: Practical Projects
-
Section 21: More Practice Exercises
-
Section 22: More Standard Library and Text Processing
-
Section 23: More Design Patterns
-
Section 24: More Projects
-
Section 25: Interview Practice
-
Section 26: C# Keywords Reference (Modifiers)
-
Section 27: C# Keywords Reference (Statements)
-
Section 28: C# Keywords Reference (Operators)
-
Section 29: BCL: System.Collections.Generic
-
Section 30: BCL: System.Linq
-
Section 31: BCL: System.Threading
-
Section 32: BCL: System.IO
-
Section 33: BCL: System.Text and System.Text.Json
-
Section 34: BCL: System.Net.Http
-
Section 35: C# Language Specification Topics
-
Section 36: More Practice Exercises
-
Section 37: More Async Patterns
-
Section 38: More BCL: System.Reflection and System.Diagnostics
-
Section 39: Nullable Reference Types In Depth
-
Section 40: C# Records and Pattern Matching In Depth
-
Section 41: Dependency Injection Deep Dive
-
Section 42: More Interview and Whiteboard Practice
223: Async Disposal with IAsyncDisposable
A few years back, I was reviewing a PR for a junior dev who had built a custom wrapper for a cloud-based messaging queue. Everything looked great until I noticed the Dispose method. Inside, they were calling a remote API to "gracefully close" the session. Since IDisposable.Dispose() is synchronous, they had used .GetAwaiter().GetResult() to force the async API call to run synchronously. The result? Intermittent, nightmare-inducing deadlocks in the production environment that only happened under heavy load. They were trying to do the right thing by cleaning up resources, but they were fighting the framework to do it.
This is exactly why IAsyncDisposable exists. When your cleanup logic involves I/O—like flushing a stream to a disk, closing a network connection, or calling a web service—doing it synchronously is a recipe for performance bottlenecks or complete application freezes. IAsyncDisposable allows you to signal to the caller that the cleanup process itself is asynchronous.
The Mechanics of DisposeAsync
Instead of the traditional Dispose() method, you implement IAsyncDisposable, which requires a single method: ValueTask DisposeAsync(). You'll notice it returns a ValueTask rather than a Task. This is a performance optimization; in many cases, the cleanup might complete synchronously, and ValueTask avoids an unnecessary heap allocation in those scenarios.
public class CloudSession : IAsyncDisposable
{
private readonly HttpClient _client = new HttpClient();
private readonly string _sessionId = Guid.NewGuid().ToString();
public async ValueTask DisposeAsync()
{
// We can actually 'await' the cleanup here!
await _client.PostAsync($"/sessions/{_sessionId}/close", null);
_client.Dispose();
Console.WriteLine("Session closed asynchronously.");
}
}
To use this class, you don't use a standard using block. Instead, you use await using. This tells the compiler to call DisposeAsync and await its completion before exiting the scope. If you use a regular using on a class that only implements IAsyncDisposable, the code simply won't compile.
Handling the Hybrid Disposal Pattern
Now, here is where it gets a bit tricky. In the real world, you often encounter classes that need to support both synchronous and asynchronous disposal—perhaps for backward compatibility or because the class is consumed by different types of callers. I usually recommend implementing both IDisposable and IAsyncDisposable in these cases.
The key is to avoid duplicating your cleanup logic. You should create a private method that handles the actual work, and have both disposal methods call into it. However, remember that the synchronous Dispose() cannot await anything. If you find yourself in a position where you must support both, but the cleanup must be async, you have a design problem. But for standard resource management, the pattern looks like this:
public class DataVault : IDisposable, IAsyncDisposable
{
public void Dispose()
{
// Perform synchronous cleanup
Cleanup();
GC.SuppressFinalize(this);
}
public async ValueTask DisposeAsync()
{
// Perform asynchronous cleanup
await CleanupAsync();
// Still call SuppressFinalize to tell the GC we're done
GC.SuppressFinalize(this);
}
private void Cleanup() { /* Sync logic */ }
private async Task CleanupAsync() { /* Async logic */ }
}
One final tip: always call GC.SuppressFinalize(this). Whether you are disposing synchronously or asynchronously, you're telling the Garbage Collector that the object has already been cleaned up, so it doesn't need to put it on the finalizer queue. It's a small detail that keeps your application's memory management snappy.
📋 Practical Task
Exercise: Implementing an Async RemoteLogBuffer
You are tasked with creating a RemoteLogBuffer class that collects logs in memory and flushes them to a remote server only when the object is disposed. Because the flush operation involves a network call, it must be handled asynchronously to prevent blocking the main thread.
Requirements:
- Implement
IAsyncDisposablein theRemoteLogBufferclass. - Create a method
public void Log(string message)that adds strings to a privateList<string>. - Implement
DisposeAsync(). Inside this method, simulate a network call by usingawait Task.Delay(100)and then print the number of logs that were "flushed" to the console. - In your
Mainmethod, instantiate theRemoteLogBufferusing theawait usingsyntax, add a few logs, and ensure the program waits for the disposal to complete.
There are no comments for now.