3

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.

Community
  • 1
  • 1
Torbjörn Kalin
  • 1,976
  • 1
  • 22
  • 31
  • Is this test-library-specific? What test framework are you using? Does this same behavior repro with other libraries? – julealgon Nov 18 '20 at 18:44

1 Answers1

3

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

dvorn
  • 3,107
  • 1
  • 13
  • 12
  • I couldn't get `DispatcherSynchronizationContext` to work (do you have an example?), but `AsyncContext` works like a charm. – Torbjörn Kalin Sep 08 '16 at 10:08
  • 1
    There are many ways to run dispatcher thread (and exit from it!) and have its sync context, but none of them are "easy". AsyncContext was purposefully created for the needs like yours. – dvorn Sep 08 '16 at 11:59
  • `AsyncContext.Run` was originally designed to provide a single-threaded context for unit tests (note that the [updated version is here](https://github.com/StephenCleary/AsyncEx.Context)). If you need something *more* (i.e., if your code uses `Dispatcher` specifically, instead of the more generic `SynchronizationContext`), then you can use [`WpfContext.Run` from here](https://github.com/StephenCleary/AsyncCTPUtil) (not yet available on NuGet). – Stephen Cleary Sep 08 '16 at 16:40