C# 5.0 Async-Feature: Unit Testing Part II, Synchronization-Context Again?

Getting Synchronisation Right Is Hard

Getting Synchronisation Right Is Hard

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

Last time we explored some implications of the C# asynchronous operations for unit tests. We noticed that we need to orchestrate tests to honor the asynchronous nature of our code. This time we take a look at another pit fall. Again this example assumes that we’re developing a small desktop application.

Let’s start: This time our business operation sums up a value, let’s say it sums up money. Since it’s hard to earn a Dollar/Euro/Swiss Franc it takes a while. And to earn more we do this money earning process twice. Our synchronous code works just fine.

The Implementation

public class ComplexBusinessOperations
{
    private int moneyEarnedSoFar;

    public int MoneyEarnedSoFar
    {
        get { return moneyEarnedSoFar; }
    }

    public void EarnMoney(int investment)
    {
        EarnMoneySubTaks(investment);
        EarnMoneySubTaks(investment);
    }

    private void EarnMoneySubTaks(int investment)
    {
        for (int i = 0; i < investment; i++)
        {
            var result = EarnOneDollar();
            moneyEarnedSoFar += result;
        }
    }

    private int EarnOneDollar()
    {
        // This operation takes a while
        Thread.Sleep(50);
        return 1;
    }
}

The Test

[Test]
public void EarnMoney()
{
    var toTest = new ComplexBusinessOperations();
    toTest.EarnMoney(200);
    Assert.AreEqual(400, toTest.MoneyEarnedSoFar);
}

As said the method ‘EarnOneDollar’ takes a while. To improve the reactiveness of our desktop application we make this process asynchronous. First we make the ‘EarnOneDollar’-method asynchronous. In the ‘EarnMoney-‘method we start the two sub task asynchronously and then wait until everything is done. In the test we wait for the result and then check if the property has the right value.

The Implementation

public class ComplexBusinessOperations
{
    private int moneyEarnedSoFar;

    public int MoneyEarnedSoFar
    {
        get { return moneyEarnedSoFar; }
    }

    public async Task EarnMoneyAsync(int investment)
    {
        var firstMoneyEarner = EarnMoneySubTaskAsync(investment);
        var secondMoneyEarner = EarnMoneySubTaskAsync(investment);
        await TaskEx.WhenAll(firstMoneyEarner, secondMoneyEarner);
    }

    private async Task EarnMoneySubTaskAsync(int investment)
    {
        for (int i = 0; i < investment; i++)
        {
            var result = await EarnOneDollarAsync();
            moneyEarnedSoFar += result;
        }
    }

    private Task<int> EarnOneDollarAsync()
    {
        return TaskEx.Run(
            () =>
                {
                    // This operation takes a while
                    Thread.Sleep(50);
                    return 1;
                });
    }
}

The Test

[Test]
public void EarnMoney()
{
    var toTest = new ComplexBusinessOperations();
    var task = toTest.EarnMoneyAsync(200);
    task.Wait();
    Assert.AreEqual(400, toTest.MoneyEarnedSoFar);
}

After this refactoring we run the tests. To our surprise the test fails sometimes. And it fails with different values!

A Race Condition, Oh noes!

Obviously we’ve introduced a race condition. However when that code runs in the regular desktop application it works fine. So why does it fail in the test setup? Remember my post about the synchronization context? This bites us here: Since there’s no synchronization context in the NUnit test-runner the continuations are executed on the thread-pool. This also means that the synchronization of our money counter variable is wrong. How do we fix that? Well we could synchronize the variable, but that’s quite ugly and bloats the code. I suggest to setup the environment so that it mimics a desktop application. This means that there’s a message pump and a synchronization context.

Let’s introduce a special ‘test’-message pump. It is based on the example implementation from my previous post.  This message pump processes messages until some special condition is matched. We create a static utility method which sets up the message pump and sets it as the synchronization context. This method expects a closure which contains the test to run. Additionally it passes an ‘awaiter’ to that test-closure. The ‘awaiter’ allows us to wait on a task and process messages meanwhile.

The Test Utility

public static class TestSyncContext
{
    public static void Run(Action<Awaiter> testToRun)
    {
        using(var msgPump = new MyPrimitiveSynchronisationContext())
        {
            var syncContextBackup = SynchronizationContext.Current;
            try
            {
                SynchronizationContext.SetSynchronizationContext(msgPump);
                var awaiter = new Awaiter(msgPump);
                msgPump.Post(obj => testToRun(awaiter), null);
                msgPump.RunOneRound();
            }
            finally
            {
                SynchronizationContext.SetSynchronizationContext(syncContextBackup);
            }
        }
    } 

}

public class Awaiter
{
    private readonly MyPrimitiveSynchronisationContext syncContext;

    internal Awaiter(MyPrimitiveSynchronisationContext syncContext)
    {
        this.syncContext = syncContext;
    }

    public void WaitFor(IAsyncResult toWaitOn)
    {
        while(!toWaitOn.IsCompleted)
        {
            syncContext.RunOneRound(); 
        }       
    }
}

The Message Pump / Synchronisation Context : Just the link, to keep this post smaller. It’s essentially the same as in my previous post.

Now we’re ready to update the test. We use this nice utility to provide a synchronization context for our test. We wrap our test in a closure and use the awaiter to wait for the process to finish

[Test]
public void EarnMoney()
{
    TestSyncContext.Run(
        awaiter=>
        {
            var toTest = new ComplexBusinessOperations();
            var task = toTest.EarnMoneyAsync(200);
            awaiter.WaitFor(task);
            Assert.AreEqual(400, toTest.MoneyEarnedSoFar);
        });
}

Conclusion And Follow-Up

This time we’ve looked at an example where the unit test fails due to invalid synchronization. It’s really important to understand the concept of the synchronization context also in unit tests. To ensure correct synchronization we’ve also introduced a utility class which sets up a special synchronization context for a test.

There’s more to come about async operations and unit testing. Meanwhile: Have a fun time playing with the Async CTP yourself.

Tagged on: , , ,

2 thoughts on “C# 5.0 Async-Feature: Unit Testing Part II, Synchronization-Context Again?

  1. Andrew

    The problem lies in two threads attempting to increment the same integer at the same time.

    moneyEarnedSoFar needs to be protected against this issue. That is the reason why your tests failed.

    google ‘thread safe increment C#’ to get thread safe methods

  2. gamlerhart Post author

    You didn’t read the post or didn’t understood it. The async operations don’t imply multithreading: The whole idea is that you don’t need to synchronize and don’t have to go into all that trouble. But this only works with a correct synchronisation-context, as explained in the post.

Leave a Reply

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