SynchronizationContext is not flowed when using aw

2019-03-30 23:13发布

问题:

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

回答1:

I had exactly same problem.

I found out it is because my custom SynchronizationContext didn't properly override and implement CreateCopy method. It seems the async code creates copy of the context after every task (or something). Make sure yours implements it properly too.



回答2:

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.