c# - 为什么 Task.ContinueWith 在此单元测试中执行失败?

标签 c# winforms unit-testing task-parallel-library

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

问题原来是因为在任务启动之前不小心创建了一个 Winforms UI 控件。

这是一个重现它的例子。您会看到,如果您按原样运行测试,它就会通过。如果您在未注释 Form 行的情况下运行测试,它将失败。

[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 never完全执行!此外,在这个单元测试中,是否正确的SynchronizationContext是无关紧要的,因为只要在任何线程上调用了waitHandle.set(),测试应该通过。

最佳答案

我忽略了您代码中的注释部分,确实在取消注释 var f = new Form();

时失败了

原因很微妙,如果 Control 类发现 SynchronizationContext.Currentnull,它会自动将同步上下文覆盖为 WindowsFormsSynchronizationContext 或其类型为 System.Threading.SynchronizationContext

一旦 Control 类用 WindowsFormsSynchronizationContext 覆盖 SynchronizationContext.Current,所有对 SendPost 的调用> 期望 Windows 消息循环运行以便工作。在您创建 Handle 并运行消息循环之前,这不会发生。

问题代码的相关部分:

internal Control(bool autoInstallSyncContext)
{
    ...
    if (autoInstallSyncContext)
    {
       //This overwrites your SynchronizationContext
        WindowsFormsSynchronizationContext.InstallIfNeeded();
    }
}

可以引用WindowsFormsSynchronizationContext.InstallIfNeeded的源码here .

如果您想覆盖 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 在评论中提到这一点)

关于c# - 为什么 Task.ContinueWith 在此单元测试中执行失败?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27191518/

相关文章:

c# - Web.Config,system.serviceModel 的外部文件

c# - 在C#WinPhone 8.1 App中播放声音-有时会导致异常

c# - 使用 ASP.net 凭据从 WinForms 应用程序登录

c# - Windows 窗体在显示消息前等待 5 秒

unit-testing - NUnit 与团队系统单元测试

python - 如何设置 django 在单元测试期间不使用内存数据库?

c# - 线程问题: Double Lists - introducing cross reference

wpf - 使用 WebBrowser 控件时 IE 11 WebGL 性能变慢

javascript - 我如何使用 jest 在 vuejs 中测试计算属性?

c# - 如何在 Java swings GUI 应用程序中访问和使用 C# dll