We´re planning to use async/await in our MVVM view models, but hit a hard issue with unit testing this code. When using NUnit and a hand written mock for our messaging we´re losing the current SynchronizationContext
.
Best shown with following small reproducing example code:
[Test] public void TestMethod()
{
Func<Task> asyncMethod = async () =>
{
var context = SynchronizationContext.Current;
await TaskEx.Yield();
Assert.AreEqual(context, SynchronizationContext.Current);
};
// Establish the new context
var syncCtx = new SingleThreadSynchronizationContext(false);
SynchronizationContext.SetSynchronizationContext(syncCtx);
// Invoke the function and alert the context to when it completes
var t = asyncMethod();
t.ContinueWith(delegate { syncCtx.Complete(); }, TaskScheduler.Default);
// Pump continuations and propagate any exceptions
syncCtx.RunOnCurrentThread();
t.GetAwaiter().GetResult();
}
Actually most of this code is stolen from the AsyncPump implementation from Stephen Toub on his blog.
Interestling all needed to make this test pass is tossing in a ExecutionContext.SuppressFlow();
before calling the async method. This could be enough to fix our Problem, but i do not know enough about ExecutionContext and i want some deeper understanding what´s going on.
Why does the code generated by the await statement swallow the current SynchronizationContext?
Is there another obvious way in using a single threaded context for unit testing async/await code?
PS: We´re using .Net4 and Microsoft.CompilerServices.AsyncTargetingPack.Net4
PPS: This also occurs in a simple project using the stable Microsoft.Bcl.Async instead of the ATP
You are running into a bug in .NET 4.0 which is fixed in .NET 4.5:
SynchronizationContext.Current is null in Continuation on the main UI thread
It is the same issue, since the code after the
await
will be wrapped in a continuation.I had exactly same problem.
I found out it is because my custom
SynchronizationContext
didn't properly override and implementCreateCopy
method. It seems the async code creates copy of the context after every task (or something). Make sure yours implements it properly too.