Async Wandering Part 1 — Why creating Form from WinForms in unit tests breaks async?

This is the first part of the Async Wandering series. For your convenience you can find other parts using the links below (or by guessing the address):
Part 1 — Why creating Form from WinForms in unit tests breaks async?
Part 2 — Synchronous call on WinForms control and ObjectDisposedException
Part 3 — Awaiting in synchronous WinForms code
Part 4 — Awaiting for void methods
Part 5 — Catching exceptions from async void
Part 6 — Exceptions logging
Part 7 — Exceptions on unobserved tasks
Part 8 — async and await — the biggest C# mistake?
Part 9 — awaiting with timeout
Part 10 — Project Loom in .NET – awaiting with fibers
Part 11 — Wrapping fibers in context
Part 12 — Fibers with generics
Part 13 — Reentrant recursive async lock
Part 14 — Async with Fibers reimplemented in .NET Core
Part 15 — How async in C# tricks you and how to order async continuations

Recently I was debugging little unit test which was creating Form (WinForms) and later was executing asynchronous method with async keyword which was causing a deadlock. Apart from the stupidity o testing UI this way (which I am not going to explain since it is irrelevant to this note), the code was so simple that there was next to nothing to spoil. The code looked similar to this:

We simply create system under test and Form, next we execute a method, and finally we assert. Nothing fancy here, however, call to DoWork never returned and the process was stalled.

Observations

The interesting thing here is: if we pass null to DoWork, everything works fine. We could try to fake it but FakeItEasy is unable to do that. So it looks like creating Form causes this whole mess. But why? Let’s dig into the code (decompiled with R#):

First, the Form constructor:

Well, it looks decent. Some flags, some state, nothing else. First line is integersing — why does it create an unused variable? Maybe this.IsRestrictedWindow does something in getter? Let’s see:

Some magic but nothing here should break async so this is a dead end. Well, let’s check class hierarchy:

Hmm, it looks like this is a derived class (which should not be a surprise). Let’s go to the base’s constructor:

Still nothing. Let’s go up:

And up:

Oh, here is something interesting. Let’s see the constructor with parameter:

Most of this is not interesting, however, parameter’s name is very suspicious: autoInstallSyncContext. Last thing of this function is:

Yes, got it. This class installs synchronization context. Let’s see it:

And we can see that it indeed replaces synchronization context with different one. We have our culprit!

Solution

What can we do? Well, we can hack it in the following way:

So we store existing context, create problematic instance, and restore the context. After this change in test everything works fine.

However… It is better to simply not test UI (or at least use interfaces or form provider).