Avoiding callbacks to the UI thread with async and WinRT

You probably already know that async operates by using the SynchronizationContext of the call site to invoke its callback. This behavior is great and makes sense for the vast majority of scenarios. There are a few, however, that it is not ideal for and it centers mostly around library developers like myself. If you didn’t know this, you may want to go watch this fantastic talk that contains a deep dive of async: The zen of async: Best practices for best performance. In fact, if you are interested in async with .NET, I recommend you watch it regardless.

I write a lot of socket based software, so I started writing my own library to provide a consistent (and higher level) API across all platforms and naturally I want it to work on WinRT. One of the first things I looked into when I got my hands on the WinRT bits was the replacement Socket API, which contains several async methods. In Tempest, once you receive some data it constructs message objects from the data, performing hash checking and sometimes even decryption. This is not code I want to be running on the UI thread for every single message.

So, I started looking around for solutions. Most I came up with involved not using the async keyword, calling out to the ThreadPool from the continuation, or using an outside thread to initiate the call. The simplest and most efficient answer I could come up with is to just not use async, but I didn’t want to accept that. Both Task.ContinueWith and IAsyncInfo.Start() do not pay attention to the SynchronizationContext, that is a feature of await:

And, just as a reminder and proof:

The same demonstration could be done for using .ContinueWith() vs. await on a Task. What I thought was that I’d be relegated to using these for advanced purposes, until I watched the talk I linked above. There’s a new method on Task, called ConfigureAwait(bool) which enables you to ignore the SynchronizationContext. What about IAsyncInfo though? There is a StartAsTask extension method on it after all, so let’s see if that works:

As it turns out, it does. Unfortunately there’s one small difference for which I can only speculate as to the reason. When you use the Task  and disable the captured context, you end up on a ThreadPool thread. However, when you use the IAsyncOperation returned directly, you still end up on a different thread, but not a ThreadPool one. One possible logical explanation is that it’s actually using WinRT’s ThreadPool to execute:

We can see here that WinRT’s ThreadPool threads do not get identified by Thread.IsThreadPoolThread.

Getting back to the code, although still an improvement on using ContinueWith, it’s still quite long. Here’s a little extension method that combines the two by just adding an overload for StartAsTask to take the same argument as ConfigureAwait.

public static ConfiguredTaskAwaitable StartAsTask (
	this IAsyncOperation self, bool continueOnContext)
{
	if (self == null)
		throw new ArgumentNullException("self");

	return self.StartAsTask()
		.ConfigureAwait (continueOnContext);
}
private async void Button_Click(object sender, RoutedEventArgs e)
{
	await DeviceInformation.FindAllAsync().StartAsTask (false);
}

So we can see that it is possible to use async without dumping back to the UI thread for calls originating from the UI thread. In some scenarios it still may not be desirable, depending on what you’re doing and your desired performance. The async keyword introduces a non-trivial amount of extra code, so if you’re concerned with the performance, I once again recommend you watch this talk.

TL;DR: Use await IAsyncInfo.StartAsTask().ConfigureAwait(false);

This entry was posted in WinRT and tagged , , , . Bookmark the permalink.
Add Comment Register



Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>