Tuesday, 18 November 2014

Async/Await

Async/Await

As I am sure you are aware, this is not a new feature in .NET - having been around for over a year as part of .NET 4.5. There have been plenty of posts about it, and so I'm not going to go into a great deal of depth, as I am not an expert and there are people who have gone into it in more depth than I could.  I just want to get a couple of  the key concepts up and provide a laymans tilt on it. 

I've been using the language feature as part of a site I am developing as a platform to testbed a few technologies including:

  • Knockout.Js, 
  • The continuous integration and continuous deployment capabilities of  Visual Studio Online (formerly Team Foundation Service)
  • WebAPI and integrating with 3rd party web services
  • Some of the newer C# language features 
  • Bootstrap templates 
  • Stanford Core NLP for .NET 
  •  
The site is work in progress and is available @
http://wearedev.azurewebsites.net/


The main purpose of the site is as a front end for a simple aggregation service for searching for development related articles, plumbing in information from a number of sources such as YouTube, MSDN, PluralSight and Stackoverflow.   I've got a few ideas about potentially leveraging the Stanford Core NLP  fpr .NET ported by Sergey Tihon so that I can, perhaps, perform slightly more accurate content matching, but its a bit of a mess at the moment. 

What is Async/Await? 

 

A C# language feature making it almost trivial to perform asynchronous work.  There are a number of different ways to already do this in C#/.NET including the Asynchrounous Programming Model (APM), think IAsyncResult, and Event  based Asynchrounous Pattern (EAP) think Background Worker.  I'm not going to discuss these much here, but suffice it to say async/await is probably a better and easier way to do async work and is also now the preferred way to do async work (I believe there are some low level optimisations to take advantage of here provided for by the TPL (Task based parallel library) which async/await sits atop of.    

In order to identify a method as something which will initiate asynchronous work,  we let the compiler know by adding the async keyword to the signature like so:



Here the Evaluate method is going to initiate some asynchronous process. It also makes this method awaitable. That is to say, a call to this method  will not block and can work in an asynchronous manner too. 

The Evaluate method will return a Task<ResultSet>.  The return type here is important.  We are not returning something that will necessarily have the value immediately but rather something that signifies that the result of some work will appear at some point in the future.    I think I've heard this same kind of concept being called a promise in other languages.  Another thing to note is that the actual return statement inside the method body will wrap the return type in Task<T>. So here ResultSet is wrapped in Task<ResultSet> impicitly.

Inside the method a call to _dataFeedRetriever.GetDataFeedResults(searchTerm) is prefixed with await.   This means that when the aforementioned method does work which does not get a result immediately,  the Evaluate method, itself, will be returned from immediately,  but crucially the position, in the Evaluate method, where execution got to, will be retained/saved so that when there are results available, normal synchronous execution will resume inside the Evaluate method.

  
Interestingly prefixing a method call with await, has the effect of unwrapping the Task<T>  type the method returns and pulling out the results when they are available.  Conversely, prefixing the return type in the signature means that the return type of the method is wrapped in a Task<T> implicitly. 

If  awaitable methods are nested,  when encountering the first await where a delay is encountered, execution returns immediately on the first await and then where each await is encountered, back up the call stack, so we back out upstream if you like.  When a result is available at the inner most await, execution resumes here and then will continue back up the call stack, perhaps without any further delay at the outer awaits. 

A misconception of async is that utilises additional threads to do its bidding, but this is not always the case.  A single thread can be used to  call out to an async operation,  such as file IO (i.e. I/O bound work).  Whilst IO work is being done and a response/result is being waited upon, the thread, used to make the request, can be freed and returned to the thread pool to be used to perform other work, such as incoming web requests.   Once the IO is completed, the thread, upon which the operation was started, can be scheduled to be used to continue execution where it left off.  

This kind of pattern allows for more efficient use of system resources and threads and potentially reduces the need to create new threads  helping save on all of the associated costs with this (TEB, TKO, User mode stack  and context switching).

Facilitating the above is a state machine which the compiler generates where the async/await keywords are encountered.   It is framework magic but I think it safe to say this is no fly by night 3rd party library but a C# language feature, which although not infallible, is pretty consistent.   

Coincidentally, the state machine which is generated for async/await is not too dissimilar to the state machine used for the yield keyword, where the position in a sequence is maintained whilst enumerating a sequence.   Jeffrey Richter leveraged the yield feature to provide an async library before it became part of the C# language.   If you are interested about Jon Skeet has both an excellent tutorial where he dissects async/await EduaAsync and also a Pluralsight video (which is probably a little easier to follow)  going into the innards of the state machine used in async/await.

Putting it all together

 

Using async/await in the Website mentioned earlier allows me  to query  the 4 feeds I mentioned at the same time,  and take advantage of only having to wait as long as the longest call to a feed, as opposed to the sum of the calls to all the feeds. At the same time making use of only one thread to do all this work and in a nice clean easy to understand manner, is a great argument for using the feature.   I'll add the rest of the code to this post or the next. 

No comments:

Post a Comment