Friday, September 28, 2018

The simple purpose of C# 's async & await

Async/Await has one single goal, to write asynchronous callbacks in a way that looks like normal everyday easy to read code.

Here is an example of retrieving a string response asynchronously using callbacks,

using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace async_with_callbacks
{
class Program
{
static void Main(string[] args)
{
var httpClient = new HttpClient();
httpClient.GetStringAsync("http://google.com").ContinueWith(getStringTask =>
{
Console.WriteLine($"Google response: {getStringTask.Result}");
}).Wait();
Console.ReadLine();
}
}
}
Compare this with the awaited version,

using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace async_with_callbacks
{
class Program
{
static async Task Main(string[] args)
{
var httpClient = new HttpClient();
var getStringTaskResult = await httpClient.GetStringAsync("http://google.com");
Console.WriteLine($"Google response: {getStringTaskResult}");
Console.ReadLine();
}
}
}
While the callback version is reasonably simple, if you continue to add more and more asynchronous calls nested inside each callback, you reach a point called "callback hell" where the code is very hard to track. Let's try where we want to do 3 asynchronous calls, in order, to retrieve 3 website's content.

using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace async_with_multiple_callbacks
{
class Program
{
static void Main(string[] args)
{
var httpClient = new HttpClient();
httpClient.GetStringAsync("http://google.com").ContinueWith(getStringTaskGoogle =>
{
httpClient.GetStringAsync("http://yahoo.com").ContinueWith(getStringTaskYahoo =>
{
httpClient.GetStringAsync("http://bing.com").ContinueWith(getStringTaskBing =>
{
Console.WriteLine($"Google response: {getStringTaskGoogle.Result}");
Console.WriteLine($"Yahoo response: {getStringTaskYahoo.Result}");
Console.WriteLine($"Bing response: {getStringTaskBing.Result}");
}, TaskContinuationOptions.AttachedToParent);
}, TaskContinuationOptions.AttachedToParent);
}).Wait();
Console.ReadLine();
}
}
}
Now compare this to the awaited version,

using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace async_with_multiple_awaits
{
class Program
{
static async Task Main(string[] args)
{
var httpClient = new HttpClient();
var getStringTaskGoogleResult = await httpClient.GetStringAsync("http://google.com");
var getStringTaskYahooResult = await httpClient.GetStringAsync("http://yahoo.com");
var getStringTaskBingResult = await httpClient.GetStringAsync("http://bing.com");
Console.WriteLine($"Google response: {getStringTaskGoogleResult}");
Console.WriteLine($"Yahoo response: {getStringTaskYahooResult}");
Console.WriteLine($"Bing response: {getStringTaskBingResult}");
Console.ReadLine();
}
}
}
Very quickly we see why await can be so much easier for humans to read and understand; it looks like normal sequential code with no callbacks required. There isn't much more to it than this, and for the majority of developers this is all you need to know to get started.