我遇到了一个单元测试失败的问题,因为 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.Current
为 null,它会自动将同步上下文覆盖为
或其类型为 WindowsFormsSynchronizationContext
System.Threading.SynchronizationContext
。
一旦 Control 类用 WindowsFormsSynchronizationContext
覆盖 SynchronizationContext.Current
,所有对 Send
和 Post
的调用> 期望 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/