Async and Await, an easy way to keep your user interface responsive
Introduction
Normally, when you invoke a method, it is invoked synchronously on your calling thread. Since you made no provision to ‘unblock’ the main thread, the method essentially ‘blocks’ the thread – which means that nothing else can happen until the method returns. What if your method was carrying out some time consuming task – such as writing a large file out to disk? Effectively, you would end up blocking the user interface (if that is where the method was invoked from), until this task was completed.
Solution : The simple solution is to use something called callbacks (part of the asynchronous programming model). The main thread simply discharges its task to a secondary thread – and says ‘call me back’ when you are done. This way, I do not need for you to finish processing.
While there are several ways to do asynchronous programming in .NET (manual use of the Thread class, delegates, BackgroundWorkers…), the most recent addition to this arsenal is the async-await duo. These two keywords simplify (somewhat) the process of executing methods asynchronously. They use callbacks behind the scenes, but thankfully, programmers are spared from having to deal with them explicitly. All we need to worry about are working with these three keywords – async, await and Task<T>.
A simple Long Running Task – carried out asynchronously
The easiest way to understand these two keywords is to see a sample. The key points are:
-
Your method uses the ‘async’ keyword in its declaration.
-
Inside your method, you will need to do two things:
- Use await somewhere inside your async method (if you fail to use await, the async method will run synchronously, Visual Studio warns you about this)
-
Use a Task<t> to return values. Think of Task<int> as a wrapper for returning an int. The returned value will be of type int and not Task<int>. A Task by itself, does not return any value.
- That’s it – that’s all we need to get started (sample code is available for download at the end of this post).
private static async void TaskWithoutBlocking()
{
Task<string> t = LongRunningTask();
Console.WriteLine("See - I came right back. No blocking");
string resultFromLongRunningTask = await t;
Console.WriteLine("Result (of long running task) = " + resultFromLongRunningTask);
}
Your Task can be ANY task – even an Http request handling task
The old way of writing a custom HTTP handler would have you inherit from IHttpHandler and override the ProcessRequest method (see example below). What if you wanted to write an async handler – because you are concerned that some of your HTTP requests will take a while to be processed – and you do not want to leave your web client hanging?
OLD WAY (Synchronous HttpHandler)
public class Handler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
var action = context.Request.QueryString["action"];
if (action == "json")
{
var json = JsonConvert.SerializeObject(new Person(), Formatting.None);
context.Response.ContentType = "application/json";
context.Response.Write(json);
}
}
}
To deal with this scenario (handling http requests asynchronously), Microsoft offers a base class (HttpTaskAsyncHandler) containing a template method (ProcessRequestAsync). All we need to do is override this ProcessRequestAsync and we are set to go. The code snippet below demonstrates how to do this.
NEW WAY (.net 4.5 – ASynchronous HttpHandler)
public class CustomHttpHandler : HttpTaskAsyncHandler
{
public override async Task ProcessRequestAsync(HttpContext context)
{
string result = await DoLongRunningTaskAsync();
}
/// <summary>
/// Simulate long running task.
/// NOTE: For simulating an async wait, we need to invoke something that returns an awaitable type (a Func is an awaitable type)
/// </summary>
private static async Task<string> DoLongRunningTaskAsync()
{
Console.WriteLine("Entered long running task");
// Thread.Sleep(5000) cannot be used - since it does not return an awaitable type;
// We need an awaitable type here, func is an acceptable awaitable type
var func = Task<string>.Factory.StartNew(() => DoLongRunningSynchronousWork());
string retValue = await func;
Console.WriteLine("Finished long running task");
return retValue;
}
/// Simulate a long running task
/// For an infinite wait - try Thread.Sleep(Timeout.Infinite)
/// For simulating heavy processory usage - while (true){}
private static string DoLongRunningSynchronousWork()
{
Stopwatch sw = new Stopwatch();
sw.Start();
Console.WriteLine("This synchronous method was called asynchronously");
Thread.Sleep(5000); // this is the long running task
sw.Stop();
return "Total time of long running synchronous method = " + sw.ElapsedMilliseconds + " milli seconds";
}
}
Summary
The async-await duo provide several powerful constructs, using just two keywords. For example, you can also cancel running tasks (although it is a bit more work).
The useful aspect of these keywords is that they can be applied to ANY task – just like you could run any task using System.Threading.Thread. However, here you can easily return types (even composite objects using Task<T>) and pass in any set of arguments. It can be useful in cases where your task seems to be tying up (blocking) the client – such as due to a long running http request.
Leave a Reply