Problem Statement
We have tests that, at some point, cause a SynchronizationContext to be set on the current nunit thread. Mixing this with await causes a dead-lock to the best of my knowledge. The issue is that we mix business logic with UI concerns all over the place. Not ideal, but this is nothing I can easily change at the moment.
Code Example
[Test]
public async Task DeadLock()
{
// force the creation of a SynchronizationContext
var form = new Form1();
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
await Task.Delay(10);
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
}
This test will dead-lock (.NET 4.6.1). I do not know why. My assumption was that the nunit thread, which kind of "becomes" the UI thread, has work in the message queue that must be drained before the continuation can be scheduled. So, for testing purposes only, I inserted call
System.Windows.Forms.Application.DoEvents();
right before the await. And here is the odd thing: The test will no longer dead-lock, but the continuation is not executed on the previous SynchronizationContext, but instead on a thread-pool thread (SynchronizationContext.Current == null and different managed thread id)! Is that obvious? Essentially, adding that call appears to behave like 'ConfigureAwait(false)'.
Does anybody know why the test dead-locks?
Assuming that it has to do with how nunit waits for async tests to complete, I thought I run the whole test in a separate thread:
[Test]
public void DeadLock2()
{
Task.Run(
async () =>
{
// force the creation of a SynchronizationContext
var form = new Form1();
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
//System.Windows.Forms.Application.DoEvents();
await Task.Delay(10);
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
}).Wait();
}
but that does not solve the problem. The 'await' is never coming back. Note that I cannot use ConfigureAwait(false) as there is code in the continuations that needs to be on the UI thread (although it removes the dead-lock).