Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Async does not continue on same thread in unit test

Given the following code:

public static async void CurrentThreadCall()
{
    Console.WriteLine("Begin on thread {0}", Thread.CurrentThread.ManagedThreadId);
    await BackgroundCall();
    Console.WriteLine("Completed on thread {0}", Thread.CurrentThread.ManagedThreadId);
}

private static async Task BackgroundCall()
{
    await Task
        .Run(() =>
            {
                Console.WriteLine("Task run on thread: {0}", Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(TimeSpan.FromMilliseconds(100));
            })
        .ConfigureAwait(false);
}

Calling the CurrentThreadCall() method from a WPF application, the Begin and Completed outputs will run on the same threads (just as I would expect):

BackgroundCall begin: 9
Task run on thread: 6
Completed on thread 9   <--- As expected

If I call the method from a unit test and use the ReSharper testrunner (2016.2 in VS2015), the Completed output will instead run on the same thread as the task:

Begin on thread 11
Task run on thread: 4
Completed on thread 4   <-- Not what I expected

Why is this, and can I do something in my test to make it work like in the WPF application?

What I have tried...

I have tried to make the test method async:

[Test]
public async Task MyTest()
{
    await CurrentThreadCall();
}

In desperation, I have tried to set the SynchronizationContext from the test:

[Test]
public void MyTest()
{
    SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
    CurrentThreadCall();
}

...with no luck.

like image 494
Torbjörn Kalin Avatar asked Mar 12 '26 01:03

Torbjörn Kalin


1 Answers

Task continuations after await run on the same synchronization context, not on the same thread. For WPF applications, the synchronization context is associated with the dispatcher, and there is only one dispatcher thread. Hence continuations run on the same thread. In unit tests there either no synchronization context or as in your example, it is default sync context associated with the thread pool. Thus, continuation may run on any of the threads in the thread pool.

If you want to reproduce the behavior exactly in tests, you should use one of single-threaded synchronization contexts - the DispatcherSynchronizationContext or e.g. https://github.com/StephenCleary/AsyncEx/wiki/AsyncContext

like image 50
dvorn Avatar answered Mar 14 '26 15:03

dvorn