C# 5.0 Async-Feature: Be Aware Of The Synchronization-Context

Disclaimer: This post is based on the C# 5.0 CTP. Everything described here is subject to future changes.

The next version of C# and VB.NET will have support for asynchronous programming. In this post I don’t want to explain how it works and what you can do with it. For an introduction watch Anders Hejlsberg’s presentation from PDC 2010. There are more resources on the CTP-site like Channel 9 videos, white-papers, examples and the CTP-binaries so that you can try the new features yourself.

I just want to focus on a very small detail of the async-feature. I assume that you’ve already informed yourself and know the basic bits. Let’s get started. As we know we can write code like this with the new async feature:

var client = new WebClient();
var webContent = await client.DownloadStringTaskAsync("http://www.gamlor.info/wordpress/");
ProcessFurther(webContent);

Now the compiler turns this code it turned inside out. The first part before the await operator is executed normally. Then rest of the method is packed into a continuation object which captures the state and logic to run asynchronous. Then the download is started, the continuation is passed to download task and the method returns immediately. As soon as the download is finished, the rest of the code is continued. As Anders Hejlsberg showed in his demo, all our code is actually executed on the same thread! The async feature doesn’t do any multi threading by itself. For example when you write a simple desktop application and use the async stuff your code is executed on the GUI-thread. You don’t need to do any locking. Let’s run this example-code in a WPF-Application and watch the output:

Console.Out.WriteLine("We are on Thread No {0}",Thread.CurrentThread.ManagedThreadId);
var client = new WebClient();
var webContentHomePage = await client.DownloadStringTaskAsync("http://www.gamlor.info/wordpress/");
Console.Out.WriteLine("We are on Thread No {0}",Thread.CurrentThread.ManagedThreadId);
Console.Out.WriteLine("Page is {0} characters long",webContentHomePage.Length);

The output is:

We are on Thread No 10
Downloaded 67770 characters, continue with the work
We are on Thread No 10

So our code is running on the same thread. This makes programming an GUI application so much easier. The async operations just work! Cool, right?

Oh rly? The Same Thread?

Ok, in reality it’s not that simple. We know that the async-feature is based on .NET 4.0 Tasks. (ok, actually on the right method-signatures, but that’s a detail). The compiler builds the continuation-state machines for us, packs those into tasks and finally composes those tasks. But how the hell does the system ensure that the code is executed on the right thread? For example on the GUI-thread? Well lets run our example code again. This time in a simple console application. The output is:

We are on Thread No 10
Downloaded 67770 characters, continue with the work
We are on Thread No 16

Oh, oh,  the code after the ‘await’ is executed on a different thread! So something seem to be wrong.

The Synchronization Context

So why do all the WPF / Silverlight examples work? Think about the process again: The whole point of the await operator is that you can instrument your code to run asynchronous while preserving the flow of your logic. However at some lower level something needs to actually run asynchronously. After it completes it calls back into our code. But it cannot ‘magically’ run code on another thread. That’s where the synchronization context comes in. The synchronization context is an abstraction which represents the ‘right’ callback context. It has been around since .NET 2.0. For example in a GUI application the synchronization context is the message-pump. In a thread pool the synchronization context could be the thread pool itself. It really depends on the context of your application.

In a WPF, Winforms, Silverlight app the synchronization context is set up for the main thread and represents the message pump. When in our example the asynchronous operation completes, it passes the continuation to the synchronization context, which adds it to the message pump. At some point in time the continuation is picked up and executed on the GUI-thread.

Your Own Synchronization Context

You can provide your own synchronization context. Of course for most application this isn’t necessary. And it’s certainly not easy to get right. Anyway, how do we get the console-application to run the async stuff on the same thread? We write a simple synchronization context which is just enough for the example. It is a message-pump like in a GUI application.

First we implement the synchronization context. To make this example simple this class implements the message pump and the synchronization context:

class MyPrimitiveSynchronisationContext : SynchronizationContext
{
    private readonly Queue<Action> messagesToProcess = new Queue<Action>();
    private readonly object syncHandle = new object();
    private bool isRunning = true;

    public override void Send(SendOrPostCallback codeToRun, object state)
    {
        throw new NotImplementedException();
    }

    public override void Post(SendOrPostCallback codeToRun, object state)
    {
        lock (syncHandle)
        {
            messagesToProcess.Enqueue(() => codeToRun(state));
            SignalContinue();
        }
    }

    public void RunMessagePump()
    {
        while(CanContinue())
        {
            Action nextToRun = GrabItem();
            nextToRun();
        }            
    }

    private Action GrabItem()
    {
        lock (syncHandle)
        {
            while (CanContinue() && messagesToProcess.Count == 0)
            {
                Monitor.Wait(syncHandle);
            }
            return messagesToProcess.Dequeue();
        }
    }

    private bool CanContinue()
    {
        lock (syncHandle)
        {
            return isRunning;
        }
    }

    public void Cancel()
    {
        lock (syncHandle)
        {
            isRunning = false;
            SignalContinue();
        }
    }

    private void SignalContinue()
    {
        Monitor.Pulse(syncHandle);
    }
}

Then we kick of the main application. We set up the synchronization context, add the regular code as initial the message to the message-pump and the start running it:

class Program
{
    static void Main(string[] args)
    {
        // Setup our synchronisation context
        MyPrimitiveSynchronisationContext ctx = new MyPrimitiveSynchronisationContext();
        MyPrimitiveSynchronisationContext.SetSynchronizationContext(ctx);

        // The first thing to process is our main application
        ctx.Post(obj=>
            MainProgramm(), null);

        // Then we kick of the message pump
        ctx.RunMessagePump();
    }

    static async void MainProgramm(){
        Console.Out.WriteLine("We are on Thread No {0}", Thread.CurrentThread.ManagedThreadId);
        var client = new WebClient();
        var webContentHomePage = await client.DownloadStringTaskAsync("http://www.gamlor.info/wordpress/");
        Console.Out.WriteLine("Downloaded {0} characters, continue with the work", webContentHomePage.Length);
        Console.Out.WriteLine("We are on Thread No {0} ", Thread.CurrentThread.ManagedThreadId);
    }
}

And now our console application works like the GUI application:

We are on Thread No 10
Downloaded 67770 characters, continue with the work
We are on Thread No 10

Conclusion

The async feature of C# and VB.NET is awesome. It really makes asynchronous programming so much easier. However don’t be fooled and believe that everything just works. I recommend you to try out the CTP and try to understand how it works.

Tagged on: , , ,

4 thoughts on “C# 5.0 Async-Feature: Be Aware Of The Synchronization-Context

  1. Nicholas

    Hi Justin,

    Great post. You were really ahead of the game considering you wrote this back in 2010. I used the above code with the hope that I could simulate thread assignment and continuation context handling logic found in UI applications. Specifically, I wanted to demonstrate common deadlock scenarios in async UI applications.

    For example, using Task.Wait() from the UI thread to block on a library method call that awaits a web service call that doesn’t use ConfigureAwait(false) is a common recipe for a deadlock. It is because the library will be trying to get back onto the UI thread context (which is tied to the UI thread) but the thread is blocked from the original call to the library because of the Task.Wait().

    Unfortunately, your code sample doesn’t cause a deadlock! LOL

  2. Nicholas

    Roman, my apologies… I got your name wrong in my last post. Feel free to correct that one and delete this one.

Leave a Reply

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