SynchronizationContext

SynchronizationContext

本文介绍了为什么Task.ContinueWith无法在这个单元测试来执行?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我所遇到的问题与失败,因为TPL任务永远不会执行其 ContinueWith单元测试(X,T​​askScheduler.FromCurrentSynchronizationContext())

I have come across a problem with a unit test that failed because a TPL Task never executed its ContinueWith(x, TaskScheduler.FromCurrentSynchronizationContext()).

原来,这个问题是因为任务开始前一个WinForms UI控件意外地被建立。

The problem turned out to be because a Winforms UI Control was accidentally being created before the Task was started.

下面是重现它的一个示例。你会看到,如果你运行测试,不被它传递。如果用表格线运行测试注释掉,它失败。

Here is an example that reproduces it. You will see that if you run the test as-is, it passes. If you run the test with the Form line uncommented, it fails.

[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()被称为在任何线程,测试应该通过。

I thought it might have been because the wrong SynchronizationContext is used, but actually, the ContinueWith never executes at all! And besides, in this unit test, whether or not it is the correct SynchronizationContext is irrelevant because as long as the waitHandle.set() is called on any thread, the test should pass.

推荐答案

我忽略了你的代码中的注释部分,事实上,

I overlooked the comments section in your code, Indeed that fails when uncommenting the var f = new Form();

原因是微妙的,变种F =新表()时失败>控制类,如果它认为将自动覆盖同步上下文 WindowsFormsSynchronizationContext SynchronizationContext.Current 或它的类型是 System.Threading.SynchronizationContext

Reason is subtle, Control class will automatically overwrite the synchronization context to WindowsFormsSynchronizationContext if it sees that SynchronizationContext.Current is null or its is of type System.Threading.SynchronizationContext.

只要控制类覆盖 SynchronizationContext.Current WindowsFormsSynchronizationContext ,所有呼叫发送发表预计,窗口消息循环为了工作运行。这是不会发生的,直到你创建的处理和运行一个消息循环。

As soon as Control class overwrite the SynchronizationContext.Current with WindowsFormsSynchronizationContext, all the calls to Send and Post expects the windows message loop to be running in order to work. That's not going to happen till you created the Handle and you run a message loop.

有问题的代码的相关部分:

Relevant part of the problematic code:

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

您可以参考源 WindowsFormsSynchronizationContext.InstallIfNeeded 的。

如果你想覆盖的SynchronizationContext ,您需要的SynchronizationContext 的自定义实现,使其工作

If you want to overwrite the SynchronizationContext, you need your custom implementation of SynchronizationContext to make it work.

解决方法:

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.");
}



上面的代码按预期工作:)

Above code works as expected :)

另外,您可以设置 WindowsFormsSynchronizationContext.AutoInstall ,这将防止同步自动覆盖背景上面提到的。(感谢OP @OffHeGoes在评论提到这一点)

Alternatively you could set WindowsFormsSynchronizationContext.AutoInstall to false, that will prevent automatic overwriting of the synchronization context mentioned above.(Thanks for OP @OffHeGoes for mentioning this in comments)

这篇关于为什么Task.ContinueWith无法在这个单元测试来执行?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-24 16:10