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

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:

[TestMethod]
public async Task DoWork_FormPassed_ExecutedSuccessfully_TrueReturned()
{
	// Arrange
	var form = new Form();
	var sut = CreateSUT();

	// Act
	var result = await sut.DoWork(form);

	// Assert
	Assert.That(result, Is.True);
}

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:

public Form()
{
  int num = this.IsRestrictedWindow ? 1 : 0;
  this.formStateEx[Form.FormStateExShowIcon] = 1;
  this.SetState(2, false);
  this.SetState(524288, true);
}

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:

/// Gets a value indicating whether the form can use all windows and user input events without restriction.
/// true if the form has restrictions; otherwise, false. The default is true.
/// 1
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public bool IsRestrictedWindow
{
  get
  {
	if (this.formState[Form.FormStateIsRestrictedWindowChecked] == 0)
	{
	  this.formState[Form.FormStateIsRestrictedWindow] = 0;
	  try
	  {
		IntSecurity.WindowAdornmentModification.Demand();
	  }
	  catch (SecurityException ex)
	  {
		this.formState[Form.FormStateIsRestrictedWindow] = 1;
	  }
	  catch
	  {
		this.formState[Form.FormStateIsRestrictedWindow] = 1;
		this.formState[Form.FormStateIsRestrictedWindowChecked] = 1;
		throw;
	  }
	  this.formState[Form.FormStateIsRestrictedWindowChecked] = 1;
	}
	return (uint) this.formState[Form.FormStateIsRestrictedWindow] > 0U;
  }
}

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

public class Form : ContainerControl

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

/// Initializes a new instance of the  class.
public ContainerControl()
{
  this.SetStyle(ControlStyles.AllPaintingInWmPaint, false);
  this.SetState2(2048, true);
}

Still nothing. Let’s go up:

/// Initializes a new instance of the  class.
public ScrollableControl()
{
  this.SetStyle(ControlStyles.ContainerControl, true);
  this.SetStyle(ControlStyles.AllPaintingInWmPaint, false);
  this.SetScrollState(1, false);
}

And up:

/// Initializes a new instance of the  class with default settings.
public Control()
: this(true)
{
}

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

internal Control(bool autoInstallSyncContext)
{
  this.propertyStore = new PropertyStore();
  this.window = new Control.ControlNativeWindow(this);
  this.RequiredScalingEnabled = true;
  this.RequiredScaling = BoundsSpecified.All;
  this.tabIndex = -1;
  this.state = 131086;
  this.state2 = 8;
  this.SetStyle(ControlStyles.UserPaint | ControlStyles.StandardClick | ControlStyles.Selectable | ControlStyles.StandardDoubleClick | ControlStyles.AllPaintingInWmPaint | ControlStyles.UseTextForAccessibility, true);
  this.InitMouseWheelSupport();
  if (this.DefaultMargin != CommonProperties.DefaultMargin)
	this.Margin = this.DefaultMargin;
  if (this.DefaultMinimumSize != CommonProperties.DefaultMinimumSize)
	this.MinimumSize = this.DefaultMinimumSize;
  if (this.DefaultMaximumSize != CommonProperties.DefaultMaximumSize)
	this.MaximumSize = this.DefaultMaximumSize;
  Size defaultSize = this.DefaultSize;
  this.width = defaultSize.Width;
  this.height = defaultSize.Height;
  CommonProperties.xClearPreferredSizeCache((IArrangedElement) this);
  if (this.width != 0 && this.height != 0)
  {
	NativeMethods.RECT lpRect = new NativeMethods.RECT();
	// ISSUE: explicit reference operation
	// ISSUE: variable of a reference type
	NativeMethods.RECT& local = @lpRect;
	int num1;
	int num2 = num1 = 0;
	// ISSUE: explicit reference operation
	(^local).bottom = num1;
	int num3;
	int num4 = num3 = num2;
	// ISSUE: explicit reference operation
	(^local).top = num3;
	int num5;
	int num6 = num5 = num4;
	// ISSUE: explicit reference operation
	(^local).right = num5;
	int num7 = num6;
	// ISSUE: explicit reference operation
	(^local).left = num7;
	CreateParams createParams = this.CreateParams;
	SafeNativeMethods.AdjustWindowRectEx(ref lpRect, createParams.Style, false, createParams.ExStyle);
	this.clientWidth = this.width - (lpRect.right - lpRect.left);
	this.clientHeight = this.height - (lpRect.bottom - lpRect.top);
  }
  if (!autoInstallSyncContext)
	return;
  WindowsFormsSynchronizationContext.InstallIfNeeded();
}

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

if (!autoInstallSyncContext)
  return;
WindowsFormsSynchronizationContext.InstallIfNeeded();

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

internal static void InstallIfNeeded()
{
  if (!WindowsFormsSynchronizationContext.AutoInstall || WindowsFormsSynchronizationContext.inSyncContextInstallation)
	return;
  if (SynchronizationContext.Current == null)
	WindowsFormsSynchronizationContext.previousSyncContext = (SynchronizationContext) null;
  if (WindowsFormsSynchronizationContext.previousSyncContext != null)
	return;
  WindowsFormsSynchronizationContext.inSyncContextInstallation = true;
  try
  {
	SynchronizationContext synchronizationContext = AsyncOperationManager.SynchronizationContext;
	if (synchronizationContext != null && !(synchronizationContext.GetType() == typeof (SynchronizationContext)))
	  return;
	WindowsFormsSynchronizationContext.previousSyncContext = synchronizationContext;
	new PermissionSet(PermissionState.Unrestricted).Assert();
	try
	{
	  AsyncOperationManager.SynchronizationContext = (SynchronizationContext) new WindowsFormsSynchronizationContext();
	}
	finally
	{
	  CodeAccessPermission.RevertAssert();
	}
  }
  finally
  {
	WindowsFormsSynchronizationContext.inSyncContextInstallation = false;
  }
}

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:

var context = SynchronizationContext.Current;
new Form();
SynchronizationContext.SetSynchronizationContext(context);

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).