<PAVE OVER> Async Performance: Understanding the Costs of async and await

Traditionally, asynchronous programming has required the expertise of skilled developers who have the time, inclination, and skill set to analyze callback after callback of non-linear control flow. Starting with the .NET Framework 4.5, the async and await keywords in C# (Async and Await in Visual Basic) make it possible for you to write asynchronous methods almost as easily as you write synchronous methods. These keywords enable you to use asynchronous programming without callbacks, without explicit marshaling of code from one synchronization context to another, without requiring an analysis of the flow of results or exceptions, and without distorting existing language features for asynchronous development. The .NET Framework provides well-optimized asynchronous method implementations, and the resulting performance is often as good as, or better than, well-written synchronous implementations that use existing patterns.

If you’re planning to develop asynchronous code in the .NET Framework, asynchronous methods should be your tool of choice. Although it’s now easy to get started writing asynchronous methods, doing it really well requires an understanding of what’s happening under the covers. Any time a language or framework raises the level of abstraction at which a developer can program, it invariably also encapsulates hidden performance costs. In many cases, such costs are negligible and can be ignored by most developers. However, advanced developers should understand these performance costs so they can take any necessary steps to avoid them. This is the case with the asynchronous methods feature in C# and Visual Basic.

Here are some simple rules for using asynchronous code:

  • Use async when you’d otherwise be blocked and waiting on the thread of execution; for example, for file I/O. If you make these latent operations asynchronous, your application will be more responsive and will provide a better user experience.

  • Use a Task object to move computational operations from your UI threads.

  • Use Task for I/O-bound activities.

  • Don’t use async for fine-grained operations, such as working on data available in memory. Use it for coarser-grained activities, such as reading from a file or a database.

  • Don’t blindly make all your code asynchronous. For example, if you are reading from a database, you might use an asynchronous method to load a record, but then use synchronous methods or properties to retrieve aspects of the loaded record.

The following example compares the time to copy a stream using both synchronous and asynchronous code.

The typical results from a run are:

Async : 00:00:04.9904232
Sync   : 00:00:03.7065544
---- 1.35x ----