Best practices and antipatterns for Async/Await

Best practices and antipatterns for Async/Await

In the previous posts, we explored how async/await works, when to use them, and what benefits they provide.

In this post and the last one in the series, we will cover some essential best practices that should be followed when using async/await and introduce common pitfalls which should be avoided.


1. Avoid using .Result and .Wait() as they are blocking calls

Blocking calls like .Result and .Wait() on an async method can cause the calling thread to wait synchronously until the asynchronous operation completes. This means the calling thread is blocked and cannot perform any other work during this time. The benefit of asynchronous execution and resource distribution will be lost. It also creates a potential deadlock situation. This is because the async method is awaiting the completion of a task, while the task is awaiting the release of the calling thread, leading to a circular dependency. Always use await instead of blocking calls like .Result or .Wait(). This ensures the method behaves asynchronously and avoids locking the thread.


2. Avoid using async void

As mentioned in the previous post, the Task object represents the ongoing asynchronous operation, and manages the state of the async operation (whether it's running, completed, or faulted). When define a method with async void, it does not return a Task object that can be awaited. The method’s caller will not know when the async void method has finished executing, which leads to unpredictable behavior in the application. And also exceptions thrown inside the async void method cannot be caught in the usual way.

The exception is Event handlers. Fire and forget - when an event is raised, the handler is executed without waiting for it to complete.


3. Use ConfigureAwait(false)

When an asynchronous method is awaited, the context in which the method was called is captured. By default, this means that the continuation after the await will run in the same synchronization context. By using ConfigureAwait(false), the awaiter will not capture the synchronization context. This means that the continuation can run on any thread available in the thread pool, rather than the original context. It can lead to a performance improvement by avoid the overhead of capturing the synchronization context and allows the continuation to run on a more efficient thread from the thread pool.


4. Use CancellationToken for Long-Running Operations

The CancellationToken is used to cancel long-running asynchronous operations. It’s typically passed as a parameter to async methods and allows to cancel the operation before it completes.

public async Task<ValueDto[]> GetValuesAsync (string filter, CancellationToken c)        

It works by providing a way to signal cancellation requests. The async method checks the IsCancellationRequested property or calls ThrowIfCancellationRequested to terminate the operation early if cancellation is requested.


5. Use Task.WhenAll for concurrent execution

We have several async calls which are not depend from each other:

await GetAppInfoAsync();
await GetUserInfoAsync();
await LoadInstructuinsAsync();        

In this case, awaiting each task sequentially introduces unnecessary delays as each task is awaited independently. Task.WhenAll() allows to run multiple asynchronous operations concurrently and waits for all of them to complete. It returns a Task that completes when all the provided tasks have finished executing. It improves performance by utilizing parallel execution for independent tasks. It reduces overall runtime by waiting for all tasks to complete simultaneously, rather than one by one.

await Task.WhenAll(GetAppInfoAsync(), GetUserInfoAsync(), LoadInstructuinsAsync());        

When using Task.WhenAll(), any exceptions thrown by the individual tasks are captured in an AggregateException. The exceptions can handled using a try-catch block around the await Task.WhenAll() statement.

Using Task.WhenAll() improves performance by utilizing parallel execution for independent tasks. It reduces overall runtime by waiting for all tasks to complete simultaneously, rather than one by one.


Conclusion

In this post we explored the best practices and antipatterns for async/await. By following these best practices, the efficiency and reliability of async code in .NET can be improved. Async programming is powerful, but incorrect usage can introduce deadlocks and performance issues.

In the next series of posts, we will delve into more advanced topics like concurrency and parallelism in async methods.

要查看或添加评论,请登录

Max Bazhenov的更多文章

  • Concurrency control in Entity Framework Core

    Concurrency control in Entity Framework Core

    Concurrency control ensures data integrity when multiple users or processes interact with the database simultaneously…

  • Use try/catch with caution

    Use try/catch with caution

    The try/catch mechanism is used for handling exceptions, allowing the program to catch errors and handle them without…

  • Thread synchronization tools in details

    Thread synchronization tools in details

    Thread management in .NET offers multiple synchronization tools, each suited to different concurrency needs.

  • Parallelism management

    Parallelism management

    Parallelism in programming allows tasks to run concurrently, making full use of multi-core CPU architectures. By…

  • What is parallelism and how does it work

    What is parallelism and how does it work

    Previous posts covered asynchronous execution where we pass tasks to external resources and use the waiting time to do…

  • Await, Async, Task in depth

    Await, Async, Task in depth

    In the previous post, we explored how asynchronous methods free up threads in the thread pool and improve WebApp…

  • WebApp and system resources

    WebApp and system resources

    In the previous post, we introduced async operations and how they can optimize WebApp performance. But how exactly do…

    1 条评论
  • ToList() vs ToListAsync(). How does it work and where is benefit

    ToList() vs ToListAsync(). How does it work and where is benefit

    In this series of posts, we will look in depth at how async works and how the load is distributed between the WebApp…

  • Bundling and minificaiton

    Bundling and minificaiton

    Bundles and minificaiton in ASP.NET 4.

  • Angular standalone components

    Angular standalone components

    A standalone component is a type of component that doesn’t belong to any specific Angular module. Standalone component…

社区洞察

其他会员也浏览了