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
.
1 2 3 4 5 |
public Task<data> GetDataAsync() { return MyWebService.FetchDataAsync() .ContinueWith(t => new Data (t.Result)); } |
1 2 3 4 5 |
public async Task<data> GetDataAsync() { var result = await MyWebService.FetchDataAsync(); return new Data (result); } |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public async Task<data> GetDataAsync(bool getLatestData) { Task task = null; if (getLatestData) task = MyWebService.FetchDataAsync(); // run some code // don't forget to chrck for Null WebData result = null; if (task != null) result = await task; return new Data (result); } |
One way to simplify the code a bit - use an empty task, which is already done. The resulting code will be cleaner:
1 2 3 4 5 6 7 8 9 |
public async Task<data> GetDataAsync(bool getLatestData) { var task = getLatestData ? MyWebService.FetchDataAsync() : Empty.Task; // run some code // task newer be null return new Data (await task); } |
Make sure that the problem is static and as a complete set. For example:
1 2 3 4 5 6 |
public static class Empty { public static Task Task { get { return _task; } } private static readonly Task _task = System.Threading.Tasks.Task.FromResult(default(T)); } |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public Task<byte[]> GetContentsOfUrl(string url) { byte[] bytes; if (_cache.TryGetValue(url, out bytes)) // additional task creates here return Task<byte[]>.Factory.StartNew(() => bytes); bytes = MyWebService.GetContentsAsync(url) .ContinueWith(t => { _cache.Add(url, t.Result); return t.Result; ); } // not thread safe (don't copy it as is) private static Dictionary<string, byte[]=""> _cache = new Dictionary<string, byte[]="">(); |
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 .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public Task<byte[]> GetContentsOfUrl(string url) { Task<byte[]> bytes; if (!_cache.TryGetValue(url, out bytes)) { bytes = MyWebService.GetContentsAsync(url); _cache.Add(url, bytes); } return bytes; } // not thread safe (don't copy it as is) private static Dictionary<string, Task<byte[]>> _cache = new Dictionary<string, Task<byte[]>>(); |
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:
1 2 3 4 5 6 |
public static async Task FooAsync() { var data = await MyWebService.GetDataAsync(); var otherData = await MyWebService.GetOtherDataAsync(); Console.WriteLine("{0} = "1", data, otherdata); } |
This will create a state object with multiple variables. See how the compiler will retain method variables:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
[StructLayout(LayoutKind.Sequential), CompilerGenerated] private struct <FooAsync>d__0 : <>t__IStateMachine { private int <>1__state; public AsyncTaskMethodBuilder <>t__builder; public Action <>t__MoveNextDelegate; public Data <data>5__1; public OtherData <otherData>5__2; private object <>t__stack; private object <>t__awaiter; public void MoveNext(); [DebuggerHidden] public void <>t__SetMoveNextDelegate(Action param0); } |
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:
1 2 3 4 5 6 7 8 9 |
public static async Task FooAsync() { var data = MyWebService.GetDataAsync(); var otherData = MyWebService.GetOtherDataAsync(); // intermediate results will get into the internal stack and // will add additional context switching between await-s Console.WriteLine("{0} = "1", await data, await otherdata); } |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public ActionResult ActionAsync() { // DEADLOCK: it blocks async task // that waits for time it can run in current context var data = GetDataAsync().Result; return View(data); } private async Task GetDataAsync() { // simple async call var result = await MyWebService.GetDataAsync(); return result.ToString(); } |
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:
1 2 3 4 5 6 7 |
public async Task ActionAsync() { // this method uses async/await and returns a Task var data = await GetDataAsync(); return View(data); } |
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.
1 2 3 4 5 6 7 |
private async Task GetDataAsync() { // ConfigureAwait(false) tells the system to allow the remaining code // execute in any context var result = await MyWebService.GetDataAsync().ConfigureAwait(false); return result.ToString(); } |
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).
12345678public async Task<data> GetContentsOfUrl(string url){// This exception will be thrown by the code, waiting to perform this taskif (url == null) throw new ArgumentNullException();var data = await MyWebService.GetContentsOfUrl();return data.DoStuffToIt();} - Exceptions thrown from the delegate tasks
Task
, will also be sent to the code, the waiting task (awaiter).
1234567891011public Task<data> GetContentsOfUrl(string url){return Task<data>.Factory.StartNew(() =>{// This exception will be thrown by the code, waiting to perform this taskif (url == null) throw new ArgumentNullException();var data = await MyWebService.GetContentsOfUrl();return data.DoStuffToIt();}} - Exceptions thrown when creating a Task, will be sent to the code that calls this method (caller) (which, in general, obviously):
1234567891011public Task<data> GetContentsOfUrl(string url){// This exception will be thrown to the calling codeif (url == null) throw new ArgumentNullException();return Task<data>.Factory.StartNew(() =>{var data = await MyWebService.GetContentsOfUrl();return data.DoStuffToIt();}}
The last example is one of the reasons why I prefer the async/await
instead of chaining tasks by Task
.
Additional links
- MSDN: Async / Await FAQ
- On optimization await "fast path"
- MSDN: Await, and UI, and deadlocks! Oh my!
- MSDN: Async Performance: Understanding the Costs of Async and Await
April 22nd, 2013 - 06:18
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!
April 26th, 2013 - 08:45
http://msdn.microsoft.com/en-us/library/vstudio/hh191443.aspx