Async/Await – Performance Overheads and Other Pitfalls
Async/Await is arguably the most powerful language feature introduced in C# 5. But what pitfalls are to be avoided? And what are the costs associated with these keywords?
The article “Best Practices in Asynchronous Programming” on MSDN highlights following points –
- Prefer async Task methods over async void methods, except for Event handlers. They have different error handling semantics. Keith Patton of Marker Metro also explains this in detail.
- Avoid mixing blocking and non-blocking code – it can cause deadlocks, more-complex error handling and unexpected blocking of context threads.
- Use ConfigureAwait(false) for better performance if the continuation code does not need the original context – it will run the continuation in a threadpool context. This can also be useful for avoiding deadlocks in case you are forced to mix synchronous and asynchronous code. Note that this has slightly different considerations when used on server-side.
- Calling a method accompanying “async” keyword involves creating a state machine and building a Task to contain the work that goes on within it, getting the execution context and the synchronization context
- In the shown example, 963 framework methods were run in order to initialize the relatively simple async method in the first call.
- The context is cached so subsequent calls have much lesser overhead
- For methods that would synchronously run in a very short timespan (say 1ms), the async overhead actually blocks the calling thread longer. In the example, it is as much as 45 ms before calling thread is unblocked. Even when run in a loop, though the subsequent calls have lesser overhead, the calling thread does not really see any performance benefit.
- Conclusion – avoid using async/await for very short methods or having await statements within tight loops (instead put the entire loop in an async method)
We’ve already covered some other common Gotchas when using these keywords.