{%Pragmatic Coding%} Tips and tricks for software developers.

24Dec/122

How to: Using async and await in C # – Best Practices

Async and await keywords, introduced in C # 5.0, greatly simplify asynchronous programming. They also hide some difficulties, if you lose a focus. It can add some problems to your code. The following practices will serve you well if you create asynchronous code for. NET applications.

Use async / await for those places that can run "a long time"

It's simple. Create Task and other structures for asynchronous operations adds some overhead. If your operation is really long-lasting, such as executing IO request, then these costs mainly will not be noticeable. And if your operation is short or take a few CPU cycles, then it may be better to perform this operation synchronously.

Overall, the team working on. NET Framework, has done a good job of choosing functionality to be asynchronous. Thus, if the method framework ends in Async , and returns the task, then you probably have to use it asynchronously.

Prefer async / await instead of Task

Writing asynchronous code using async/await , and greatly simplifies the process of creating the code and read, rather than use of the Task .

In terms of performance, the two methods presented above have low overhead, but they are somewhat different scale with the number of tasks including:

  • Task is building a chain of sequels, which increases according to the number of tasks related series, and the system state is controlled by fault found by the compiler.
  • Async/await builds a state machine that uses no more resources to add new steps. However, the compiler can determine more variables on the stack for saving the state machine, depending on your code (and the compiler). In the article on MSDN perfectly described details of what is happening.

In most scenarios, the async/await will use less resources and run faster than the Task .

Use already performed an empty static task for the conditional code

Sometimes you want to run the task only when a certain condition. Unfortunately, await cause NullReferenceException , if he receives a null instead of the task, and the processing of this will make your code less readable.

One way to simplify the code a bit - use an empty task, which is already done. The resulting code will be cleaner:

Make sure that the problem is static and as a complete set. For example:

Performance: Prefer cache tasks themselves, rather than their data

There is some overhead in creating tasks. If you cache your results, but then convert them back to the task, you may create additional task objects.

Instead of it, would be better to copy the cache tasks themselves. In this case, using their code may have to wait for the task execution. The Task Parallel Library contains optimizations for the code to run a pending task is completed at higher speeds .

Performance: Understand how await saves the state

When you use async/await , the compiler creates a state machine that stores the variables and stack. For example:

This will create a state object with multiple variables. See how the compiler will retain method variables:

Note 1. If you declare a variable, it is stored in the object stored state. This can lead to the fact that objects will remain in the memory longer than you would have ever expected.
Note 2. But if you're not going to declare a variable, and use the value of Async call with await , variable gets into the internal stack:

You don't have too much to worry about as long as you don't see performance issues. If you will decide to go into the optimization, MSDN has a good article on this subject: Async Performance: Understanding the Costs of Async and Await .

Stability: async / await - is not a Task.Wait

The state machine generated async/await - is not the same thing as Task.ContinueWith/Wait . In general, you can replace the implementation of a Task to await , but there may be some performance issues and stability. Let's look in more detail.

Stability: Know your synchronization context

Code. NET is always executed in a certain context. This context determines the current user and other values ​​required by the framework. In some contexts, the implementation, the code works in the context of synchronization, which manages the execution of tasks and the other asynchronous.

By default, after await the code will continue to work in a context in which it was launched. This is convenient, because basically you want to security context has been restored, and you want your code after await had access to objects Windows UI, if he had access to them at the start. Note that Task.Factory.StartNew - does not provide the context of recovery.

Some contexts don't support the synchronization log back in, and are single-threaded. This means that only one unit of work can be done in this context at the same time. An example of this might be a thread or context of the Windows UI ASP.NET.

In these single-thread synchronization context quite easily get deadlock. If you create a task in the context of a single-thread, and then you wait in the same context, your code that is waiting to block the execution of a background task.

Stability: Do not Wait , to wait until the problem is right here

As a general rule - if you create asynchronous code, be careful using a Wait . (With await all a little better.)

Do not use the Wait for problems in single-thread synchronization contexts, such as:

  • UI flows
  • Context ASP.NET

The good news is that the framework allows you to return a Task in certain cases, and the framework itself will be expected to perform the task. Trust him with this process:

If you create asynchronous library, your users will need to write asynchronous code. Previously, this was a problem, because writing asynchronous code was tedious and susceptible to errors, but with the advent ofasync/await most of now handled by the compiler. And your code gets more reliable, and now you are less likely to be forced to deal with the nuances of ThreadPool .

Stability: consider using ConfigureAwait , in case you create a library

If you have to wait for the task in one of these contexts, you can use ConfigureAwait, to tell the system that it should not perform a background task in your context. The disadvantage of this is that the background task will not have access to the same synchronization context, so that you will lose access to Windows UI or HttpContext (although your security context will still be with you).

If you create a "library" function, which returns a Task , you probably do not know how it will be called. So, it may be safer to add ConfigureAwait(false) to your problem before it is returned.

Stability: Understand the behavior of exceptions

When you look at the asynchronous code, it is hard sometimes to say what happens with exceptions. Whether it will be transferred to the caller, or to the code that expects the task?

The rules in this case are pretty straightforward, but still sometimes difficult to answer the question, just looking at the code.

Some examples:

  • Exceptions thrown out of the async / await method that will be sent to the code, the waiting task (awaiter).
  • Exceptions thrown from the delegate tasks Task , will also be sent to the code, the waiting task (awaiter).
  • Exceptions thrown when creating a Task, will be sent to the code that calls this method (caller) (which, in general, obviously):

The last example is one of the reasons why I prefer the async/await instead of chaining tasks by Task .

Additional links

Share on social network

Share to Facebook
Share to Google Plus
Share to LiveJournal
Share to MyWorld
Share to Odnoklassniki
Share to Yandex
Comments (2) Trackbacks (0)
  1. Hi there! This is my first comment here so I just wanted to give a quick shout out and tell you I really enjoy reading through your blog posts. Can you recommend any other blogs/websites/forums that cover the same subjects? Thanks!


Leave a comment

No trackbacks yet.