为什么在等待WindowsFormsSynchronizationContext时mstest会挂起?

时间:2013-12-16 21:14:16

标签: winforms async-await mstest

我有一个自定义Windows窗体控件,其中包含MSTest单元测试。我在这个类中添加了一个异步方法,我需要覆盖它,但新测试总是超时。

经过一些实验,我把问题简化为控制创建和等待。

[TestMethod]
public async Task TestMethod1()
{
    Control c = new Control();
    await Task.Delay(1);
}

我注意到测试始于SynchronizationContext.Current设置为null,然后创建控件后,它会更改为WindowsFormsSynchronizationContext。如果我强制同步上下文回到null,那么测试将通过。

[TestMethod]
public async Task TestMethod1()
{
    Control c = new Control();
    SynchronizationContext.SetSynchronizationContext(null);
    await Task.Delay(1);
}

有没有办法从WindowsFormsSynchronizationContext无阻塞地使用等待?

1 个答案:

答案 0 :(得分:7)

Windows窗体控件假定它们在Windows窗体应用程序中运行,而单元测试则不然。这就是你的单元测试挂起的原因。

更具体地说,WinForms控件安装WindowsFormsSynchronizationContext委托工作到Application.Run内的Win32消息循环。因此,您的async方法会查看上下文并将其继续排入该消息循环。但是,没有实际的消息循环,因为您的单元测试不会调用Application.Run

最佳解决方案IMO将使用MVVM模式,您不在UI应用程序之外测试UI元素,因此您永远不会遇到这种情况(在MVVM中,您可以对逻辑 UI进行单元测试但不是 literal UI)。使用MVVM方法,您的代码将如下所示:

[TestMethod]
public async Task TestMethod1()
{
  ViewModel vm = new ViewModel();
  await vm.MethodAsync();
}

但是如果你想对实际的UI元素进行单元测试,你可以使用Async CTP中包含的类似Windows Forms Context的类型:

[TestMethod]
public async Task TestMethod1()
{
    await WindowsFormsContext.Run(async () =>
    {
        Control c = new Control();
        await Task.Delay(1);
    });
}