鉴于一些评论,我应该明确指出,这个问题是关于为什么 TestScheduler 抛出空引用异常,而不是如何让测试通过。 前面的示例假设与 TPL 的交互是问题的原因,但现在我发现这不是触发行为所必需的,所以我用更简单的测试用例替换了代码
我在尝试将 rx 测试"虚拟时间"TestScheduler 与后台线程相结合时遇到一些问题。 我发现演示问题的最简单方法显示在帖子底部。
该代码只是运行一个后台线程,该线程订阅具有超时的可观察序列。
超时是由 TestScheduler 驱动的,当我在主线程上推进超时时,正在生成一个空引用异常:
Assert.Fail 失败。虚拟时间 00:00:00.1394720,异常 System.NullReferenceException:对象引用未设置为对象的实例。 at System.Reactive.Concurrency.VirtualTimeScheduler
2.GetNext() at System.Reactive.Concurrency.VirtualTimeSchedulerBase
2.AdvanceTo(TAbsolute time( at System.Reactive.Concurrency.VirtualTimeSchedulerBase'2.AdvanceBy(TRelative time( at UnitTestProject1.UnitTest1.d__6.MoveNext(( in ....\UnitTest1.cs:line
该故障似乎对使用的 IObservable 的确切类型不敏感,但似乎取决于超时选择器的存在。 有趣的是,测试通常会在超时因火灾而失败之前很久的虚拟时间失败。
运行与控制台应用程序相同的代码似乎也可以工作,尽管问题似乎很容易受到干扰(可能是争用条件(,因此这可能是一个红鲱鱼。
进一步的研究和评论强烈暗示,该行为是由于在后台线程计划其超时操作时调度程序前进时发生的争用条件
提前感谢您对此的任何了解...
using Microsoft.Reactive.Testing;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Reactive.Linq;
using System.Threading;
namespace UnitTestProject1
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestBackgroundThreadWithTestScheduler()
{
var scheduler = new TestScheduler();
var seq = Observable.Never<string>();
bool subscribed = false;
ThreadPool.QueueUserWorkItem(_ =>
{
Thread.Sleep(100); //wait a bit to give main thread chance to start advancing scheduler
seq.Timeout(TimeSpan.FromSeconds(10), scheduler)
.Subscribe(s => {/*never called*/});
subscribed = true; //signal we're subscribed
});
//---- Uncommenting this line to avoid the race condition appears to fix the test ----
//while (!subscribed) Thread.Yield();
//Advance the scheduer in small increments to maximise our chances of hitting the race
var watch = scheduler.StartStopwatch();
try
{
while (watch.Elapsed < TimeSpan.FromSeconds(20)) scheduler.AdvanceBy(10);
}
//NullReference is thrown unexpectedly
catch (NullReferenceException ex)
{
Assert.Fail("Virtual time {0}, exception {1}", watch.Elapsed, ex);
}
catch (TimeoutException)
{
//desired result is a TimeoutException so this is a test pass
}
}
}
}
跟踪System.Reactive.Concurrency.VirtualTimeScheduler2.GetNext()
中的这一行是一个线索,表明错误确实是您正在从 2 个线程访问调度程序(TestScheduler 不是线程安全的,当前从多个线程访问它时TestScheduler
中存在错误(。
第一条评论可能是正确的:后台任务正在添加到计划程序,就像您的其他任务正在推进计划程序一样。
尝试在访问调度程序的语句周围添加锁,看看是否可以解决您的问题。
当然,真正的解决方法是在后台任务设置好后发出信号,并让测试任务等待信号后再继续。 因为即使TestScheduler
没有其 bug,您的测试也具有一个争用条件,即测试线程可以在后台线程订阅可观察量之前完成。