Brief explanation of Async/Await in .Net 4.5

2019-01-13 10:05发布

问题:

How does Asynchronous tasks (Async/Await) work in .Net 4.5?

Some sample code:

private async Task<bool> TestFunction()
{
  var x = await DoesSomethingExists();
  var y = await DoesSomethingElseExists();
  return y;
}

Does the second await statement get executed right away or after the first await returns?

回答1:

await pauses the method until the operation completes. So the second await would get executed after the first await returns.

For more information, see my async / await intro or the official FAQ.



回答2:

It executes after the first await returns. If this thing confuses you, try to play around with breakpoints - they are fully supported by the new async pattern.

Imagine it would look like this:

var x = await GetSomeObjectInstance();
var y = await GetSomeObjectInstance2(x);

There probably would occur a NullReferenceException somewhere, so the first await has to return first. Otherwise, x would be null/undefined or whatever.



回答3:

The methods calls will still occur sequentially just like "regular", non awaited method calls. The purpose of await is that it will return the current thread to the thread pool while the awaited operation runs off and does whatever.

This is particularly useful in high performance environments, say a web server, where a given request is processed on a given thread from the overall thread pool. If we don't await, then the given thread processing the request (and all it's resources) remains "in use" while the db / service call completes. This might take a couple of seconds or more especially for external service calls.

Now in low traffic websites this is not much of an issue but in high traffic sites the cost of all these request threads just sitting around, doing nothing, in an "in-use" state, waiting for other processes like those db /service calls to return can be a resource burden.

We are better off releasing the thread back to the worker pool to allow it do other useful work for some other request.

Once the db / service call completes, we can then interrupt the thread pool and ask for a thread to carry on processing that request from where it left off. At that point the state of the request is reloaded and the method call continues.

So on a per request basis when using await, the request will still take the same amount of time from the users perspective... plus a tiny smidge more for the switching overhead.

But in the aggregate, across all requests for all users, things can seem more performant to all users as the web server (in this case) runs more efficiently with better resource utilization. i.e. it either doesn't have to queue up requests waiting for free threads to process requests because await is returning them or alternatively we don't have to buy more hardware because we are using the same amount of hardware, more efficiently, to obtain higher throughputs.

There is a switching cost to this though so despite what you see in the default templates and in many docs you shouldn't just blindly use await for every single call. It's just a tool and like all tools it has its place. If the switching cost is not less than the cost of just completing your calls synchronously then you shouldn't use await.