C# 为什么Task.ContinueWith在此单元测试中无法执行?

C# 为什么Task.ContinueWith在此单元测试中无法执行?,c#,winforms,unit-testing,task-parallel-library,C#,Winforms,Unit Testing,Task Parallel Library,我遇到了一个单元测试失败的问题,因为TPL任务从未使用(x,TaskScheduler.FromCurrentSynchronizationContext())执行其ContinueWith 问题的原因是在任务启动之前意外创建了Winforms UI控件 下面是一个复制它的示例。您将看到,如果按原样运行测试,它将通过。如果在未注释表单行的情况下运行测试,则测试将失败 [TestClass] public class UnitTest1 { [TestMethod] public

我遇到了一个单元测试失败的问题,因为TPL任务从未使用(x,TaskScheduler.FromCurrentSynchronizationContext())执行其
ContinueWith

问题的原因是在任务启动之前意外创建了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()
,测试就应该通过。

注释掉该行后,您的
SynchronizationContext
就是您创建的默认上下文。这将导致
TaskScheduler.fromCurrentSynchrongZisationContext()
使用默认调度程序,该调度程序将在线程池上运行延续

创建类似表单的Winforms对象后,当前的
SynchronizationContext
将变为,这反过来将返回一个调度程序,该调度程序依赖于Winforms消息泵来调度继续


由于单元测试中没有WinForms pump,因此无法运行continuation。

我忽略了代码中的注释部分,事实上,在取消对
var f=new Form()的注释时,注释失败

原因很微妙,
Control
类将自动将同步上下文覆盖到
windowsformsssynchronizationcontext
,如果它看到
SynchronizationContext。当前的
null
或其类型为
System.Threading.SynchronizationContext

一旦控制类用
windowsformsssynchronizationcontext
覆盖了
SynchronizationContext.Current
,对
Send
Post
的所有调用都希望windows消息循环运行以正常工作。在创建
句柄
并运行消息循环之前,不会发生这种情况

问题代码的相关部分:

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.");
}
以上代码按预期工作:)


或者,您可以将
windowsformsssynchronizationcontext.AutoInstall
设置为
false
,这将防止自动覆盖上述同步上下文。(感谢OP@OffHeGoes在评论中提到这一点)

是否发生异常?@SriramSakthivel愚蠢的问题-您确实尝试了
var f=new Form()未注释?我是MsTest,VS2013u4,Win8.1,Net 4.5。1@OffHeGoes对不起,我忽略了。现在写一个答案:)@YuvalItzchakov不,也不例外。如果我删除
TaskScheduler.FromCurrentSynchronizationContext()!知道这一点真的很有用。我刚刚查看了
WindowsFormsSynchronizationContext.InstallIfNeeded()
,可以看到它检查
typeof(SynchronizationContext)
。还注意到您也可以这样做来停止它:
WindowsFormsSynchronizationContext.AutoInstall=false@Sriram伟大的答案+1任务-我在尝试调试问题时正在检查TaskScheduler.Current
,我应该检查SynchronizationContext.Current