C# 为什么Task.ContinueWith在此单元测试中无法执行?
我遇到了一个单元测试失败的问题,因为TPL任务从未使用(x,TaskScheduler.FromCurrentSynchronizationContext())执行其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
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
。