Off*_*oes 10 c# unit-testing winforms task-parallel-library
我遇到了单元测试失败的问题,因为TPL任务从未执行过它ContinueWith(x, TaskScheduler.FromCurrentSynchronizationContext()).  
问题原因是因为在启动任务之前意外创建了Winforms UI控件.
这是一个再现它的例子.您将看到,如果您按原样运行测试,它将通过.如果在未取消注释表单行的情况下运行测试,则会失败.
[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestMethod1()
    {
        // Create new sync context for unit test
        SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
        var waitHandle = new ManualResetEvent(false);
        var doer = new DoSomethinger();
        //Uncommenting this line causes the ContinueWith part of the Task
        //below never to execute.
        //var f = new Form();
        doer.DoSomethingAsync(() => waitHandle.Set());
        Assert.IsTrue(waitHandle.WaitOne(10000), "Wait timeout exceeded.");
    }
}
public class DoSomethinger
{
    public void DoSomethingAsync(Action onCompleted)
    {
        var task = Task.Factory.StartNew(() => Thread.Sleep(1000));
        task.ContinueWith(t =>
        {
            if (onCompleted != null)
                onCompleted();
        }, TaskScheduler.FromCurrentSynchronizationContext());
    }
}
任何人都可以解释为什么会这样吗?
我认为这可能是因为使用了错误 SynchronizationContext,但实际上,ContinueWith 从来没有执行过!此外,在这个单元测试中,它是否正确SynchronizationContext是无关紧要的,因为只要waitHandle.set()在任何线程上调用,测试就应该通过.
我忽略了代码中的注释部分,确实在取消注释时失败了 var f = new Form();
原因是微妙的,Control类将自动覆盖同步上下文WindowsFormsSynchronizationContext如果它看到SynchronizationContext.Current是null或其IS类型的System.Threading.SynchronizationContext.
一旦Control类覆盖了SynchronizationContext.Currentwith WindowsFormsSynchronizationContext,所有调用Send并Post期望windows消息循环运行以便工作.在您创建Handle并运行消息循环之前,这不会发生.
问题代码的相关部分:
internal Control(bool autoInstallSyncContext)
{
    ...
    if (autoInstallSyncContext)
    {
       //This overwrites your SynchronizationContext
        WindowsFormsSynchronizationContext.InstallIfNeeded();
    }
}
你可以参考WindowsFormsSynchronizationContext.InstallIfNeeded 这里的来源.
如果要覆盖SynchronizationContext,则需要自定义实现SynchronizationContext才能使其正常工作.
解决方法:
internal class MyContext : SynchronizationContext
{
}
[TestMethod]
public void TestMethod1()
{
    // Create new sync context for unit test
    SynchronizationContext.SetSynchronizationContext(new MyContext());
    var waitHandle = new ManualResetEvent(false);
    var doer = new DoSomethinger();
    var f = new Form();
    doer.DoSomethingAsync(() => waitHandle.Set());
    Assert.IsTrue(waitHandle.WaitOne(10000), "Wait timeout exceeded.");
}
上面的代码按预期工作:)
或者你可以设置WindowsFormsSynchronizationContext.AutoInstall为false,这将阻止自动覆盖上面提到的同步上下文.(感谢OP @OffHeGoes在评论中提到这一点)