24 Essential Async/Await Best Practices for Basic to Advanced C# Developers
Tags: programming, coding, csharp, webdev
Published: 2024-04-04 16:34:00

Async/await in C# is a framework used for writing asynchronous C# code that is both readable and maintainable. These tips will help you to integrate async/await programming more effectively in the # projects:

## 1. ValueTask for Lightweight Operations

Use ValueTask instead of Task for asynchronous methods that often complete synchronously, reducing the allocation overhead.
```csharp
    public async ValueTask<int> GetResultAsync()
    {
        if (cachedResult != null)
            return cachedResult;
        int result = await ComputeResultAsync();
        cachedResult = result;
        return result;
    }
```
## 2. ConfigureAwait for Library Code

Use ConfigureAwait(false) in library code to avoid deadlocks by not capturing the synchronization context.
```csharp
    public async Task SomeLibraryMethodAsync()
    {
        await SomeAsyncOperation().ConfigureAwait(false);
    }
```
## 3. Avoiding async void

Prefer async Task over async void except for event handlers, as async void can lead to unhandled exceptions and is harder to test.
```csharp
    public async Task EventHandlerAsync(object sender, EventArgs e)
    {
        await PerformOperationAsync();
    }
```
## 4. Using IAsyncDisposable

For asynchronous cleanup, implement IAsyncDisposable and use await using to ensure resources are released properly.
```csharp
    public async ValueTask DisposeAsync()
    {
        await DisposeAsyncCore();
    }
    
    private async ValueTask DisposeAsyncCore()
    {
        if (resource != null)
        {
            await resource.DisposeAsync();
        }
    }
```
## 5. Efficiently Combine Tasks

Use Task.WhenAll for running multiple tasks in parallel and waiting for all to complete, which is more efficient than awaiting each task sequentially
```csharp
    public async Task ProcessTasksAsync()
    {
        Task task1 = DoTask1Async();
        Task task2 = DoTask2Async();
        await Task.WhenAll(task1, task2);
    }
```
## 6. Cancellation Support

Support cancellation in asynchronous methods using CancellationToken.
```csharp
    public async Task DoOperationAsync(CancellationToken cancellationToken)
    {
        await LongRunningOperationAsync(cancellationToken);
        cancellationToken.ThrowIfCancellationRequested();
    }
```
## 7. State Machine Optimization

For performance-critical code, consider structuring your async methods to minimize the creation of state machines by separating synchronous and asynchronous paths.
```csharp
    public async Task<int> FastPathAsync()
    {
        if (TryGetCachedResult(out int result))
        {
            return result;
        }
        return await ComputeResultAsync();
    }
```
## 8. Avoid Blocking Calls

Avoid blocking on async code with .Result or .Wait(). Instead, use asynchronous waiting through the stack.
```csharp
    public async Task WrapperMethodAsync()
    {
        int result = await GetResultAsync();
    }
```
## 9. Eliding Async/Await

In simple passthrough scenarios or when returning a task directly, you can elide the async and await keywords for slightly improved performance.
```csharp
    public Task<int> GetResultAsync() => ComputeResultAsync();
```
## 10. Custom Task Schedulers

For advanced scenarios, like limiting concurrency or capturing synchronization contexts, consider implementing a custom TaskScheduler.
```csharp
    public sealed class LimitedConcurrencyLevelTaskScheduler : TaskScheduler
    {
        // Implement the scheduler logic here.
    }
```
## 11. Using Asynchronous Streams

Leverage asynchronous streams with IAsyncEnumerable<T> for processing sequences of data asynchronously, introduced in C# 8.0.
```csharp
    public async IAsyncEnumerable<int> GetNumbersAsync()
    {
        for (int i = 0; i < 10; i++)
        {
            await Task.Delay(100); // Simulate async work
            yield return i;
        }
    }
```
## 12. Avoid Async Lambdas in Hot Paths

Async lambdas can introduce overhead. In performance-critical paths, consider refactoring them into separate async methods.
```csharp
    // Before optimization
    Func<Task> asyncLambda = async () => await DoWorkAsync();
    
    // After optimization
    public async Task DoWorkMethodAsync()
    {
        await DoWorkAsync();
    }
```
## 13. Use SemaphoreSlim for Async Coordination

SemaphoreSlim can be used for async coordination, such as limiting access to a resource in a thread-safe manner.
```csharp
    private readonly SemaphoreSlim semaphore = new SemaphoreSlim(1, 1);
    
    public async Task UseResourceAsync()
    {
        await semaphore.WaitAsync();
        try
        {
            // Access the resource
        }
        finally
        {
            semaphore.Release();
        }
    }
```
## 14. Task.Yield for UI Responsiveness

Use await Task.Yield() in UI applications to ensure the UI remains responsive by allowing other operations to process.
```csharp
    public async Task LoadDataAsync()
    {
        await Task.Yield(); // Return control to the UI thread
        // Load data here
    }
```
## 15. Asynchronous Lazy Initialization

Use Lazy<Task<T>> for asynchronous lazy initialization, ensuring the initialization logic runs only once and is thread-safe.
```csharp
    private readonly Lazy<Task<MyObject>> lazyObject = new Lazy<Task<MyObject>>(async () =>
    {
        return await InitializeAsync();
    });
    
    public async Task<MyObject> GetObjectAsync() => await lazyObject.Value;
```
## 16. Combining async and LINQ

Be cautious when combining async methods with LINQ queries; consider using asynchronous streams or explicitly unwrapping tasks when necessary.
```csharp
    public async Task<IEnumerable<int>> ProcessDataAsync()
    {
        var data = await GetDataAsync(); // Assume this returns Task<List<int>>
        return data.Where(x => x > 10);
    }
```
## 17. Error Handling in Async Streams

Handle errors gracefully in asynchronous streams by encapsulating the yielding loop in a try-catch block.
```csharp
    public async IAsyncEnumerable<int> GetNumbersWithErrorsAsync()
    {
        try
        {
            for (int i = 0; i < 10; i++)
            {
                if (i == 5) throw new InvalidOperationException("Test error");
                yield return i;
            }
        }
        catch (Exception ex)
        {
            // Handle or log the error
        }
    }
```
## 18. Use Parallel.ForEachAsync for Asynchronous Parallel Loops

Utilize Parallel.ForEachAsync in .NET 6 and later for running asynchronous operations in parallel, providing a more efficient way to handle CPU-bound and I/O-bound operations concurrently.
```csharp
    await Parallel.ForEachAsync(data, async (item, cancellationToken) =>
    {
        await ProcessItemAsync(item, cancellationToken);
    });
```
## 22. Avoid Excessive Async/Await in High-Performance Code

In performance-critical sections, minimize the use of async/await. Instead, consider using Task.ContinueWith with caution or redesigning the workflow to reduce asynchronous calls.
```csharp
    Task<int> task = ComputeAsync();
    task.ContinueWith(t => Process(t.Result));
```
## 23. Async Main Method

Utilize the async entry point for console applications introduced in C# 7.1 to simplify initialization code.
```csharp
    public static async Task Main(string[] args)
    {
        await StartApplicationAsync();
    }
```
## 24. Optimize Async Loops

For loops performing asynchronous operations, consider batching or parallelizing tasks to improve throughput.
```csharp
    var tasks = new List<Task>();
    for (int i = 0; i < items.Length; i++)
    {
        tasks.Add(ProcessItemAsync(items[i]));
    }
    await Task.WhenAll(tasks);
```
### More Cheatsheets
[Cheat Sheets - .Net](https://singhsukhpinder.medium.com/list/00f13bef4aa2)

### C# Programming🚀

Thank you for being a part of the C# community! Before you leave:

Follow us: [Youtube](https://www.youtube.com/channel/UCejYGg4gAOa1uPAObFuk1HQ) | [X](https://twitter.com/sukhsukhpinder) | [LinkedIn](https://www.linkedin.com/in/sukhpinder-singh/) | [Dev.to](https://dev.to/ssukhpinder) 
Visit our other platforms: [GitHub](https://github.com/ssukhpinder) 
More content at [C# Programming](https://medium.com/c-sharp-progarmming)