Monday, 17 March 2014

Async and await with external web resources

Async and await are two keywords that have been available in the C# for a while now. And so whilst probably a bit late to the party thought would share how I've used them for a recent feature, in case they are also new to others.

They allow you to write async code much more easily than was previously possible, which should allow applications to scale better - particularly when there's a need to call out to slow running processes (e.g. external web services), or really anything that is "IO bound" (like a database call.

So for example you could speed up a web request that requires two calls to two long running processes by running them in parallel on different threads and then waiting for both to return. If they both take 1 second your result can return in 1 second instead of 2.

But even with just one long running process in the request, it's still useful, as whilst that process is running the thread serving the request is no longer blocked, and can return to the pool to process other requests. Meaning IIS under load doesn't run out of threads and stop serving content even for simpler or static pages that don't require access to the resource.

Here's the before. I have a controller action that calls up to a service layer to get and parse content from an RSS feed. It ends up at this helper method:

private List<ExternalContentItem> GetItemsFromExternalSource(string url, string category)
 {
    var feedXML = XDocument.Load(url);
    return _rssParser.GetItemsFromXml(feedXML, category).ToList();
 }

And the after. Various long-running methods in .Net 4.5 have been updated to have async versions. XDocument.Load isn't one of them, but if you drop down to the HttpClient you can do this:

private async Task<List<ExternalContentItem>> GetItemsFromExternalSourceAsync(string url, string category)
{
    var httpClient = new HttpClient();
    var response = await httpClient.GetAsync(url).ConfigureAwait(false);
    if (response.StatusCode == HttpStatusCode.OK)
    {
        var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);

        var feedXML = XDocument.Load(stream);
        return _rssParser.GetItemsFromXml(feedXML, category).ToList();
    }

    return new List<ExternalContentItem>();
}

To briefly explain the key points:

  • The async keyword does nothing other than to allow you to use await.
  • Await tells the compiler to convert this into async code, so the thread is returned to the pool and code resumes once the call is complete.
  • Your return type needs to change to be a Task of whatever you actually want back.
  • Your calling code can get the result by calling the .Result property on the task.
  • Those ConfigureAwait(false) calls are apparently necessary to avoid deadlocks. They aren't needed if you are using async code throughout the request, but as I'm just amending this method I'm led to believe they are needed here.
  • One other oddity is exception handling. Any exceptions get aggregated up into a new exception type, which is a bit harder to deal with. That's why I've added that check on the status code rather than using a try/catch in the calling code which I had before.

Note important point that this code won't actually run any faster. It's just that it won't block whilst it's waiting.

For this site, this was probably just an academic exercise - I have caching in place too so these calls are very few and far between. But I think this is becoming more and more of an important technique to be aware of.

With MVC we can now create async controllers and with Entity Framework 6 there will also be asynchronous data access. Which means you can be async all the way up and down, and given that, it'll probably be a question of "why not?" rather than "why?" to use them. I can see in the not too distant future it being just the default way to do things.

No comments:

Post a Comment