当后台线程调度操作时,Rx TestScheduler抛出NullReference异常

时间:2014-10-09 20:50:25

标签: c# system.reactive

根据一些评论,我应该清楚地说明这个问题是为什么TestScheduler抛出一个空引用异常,而不是如何让测试通过。前面的例子假设与TPL的交互是问题的原因,但我现在发现这不是触发行为所必需的,所以我用更简单的测试用例替换了代码

我在尝试将rx-testing'虚拟时间'TestScheduler与后台线程结合起来时遇到了一些问题。我发现证明问题的最简单方法显示在帖子的底部。

代码只是运行一个后台线程,该线程订阅具有超时的可观察序列。

超时是从TestScheduler驱动的,当我在主线程上推进时,正在生成一个空引用异常:

  

Assert.Fail失败。虚拟时间00:00:00.1394720,异常System.NullReferenceException:未将对象引用设置为对象的实例。     在System.Reactive.Concurrency.VirtualTimeScheduler 2.GetNext() at System.Reactive.Concurrency.VirtualTimeSchedulerBase 2.AdvanceTo(TAbsolute time)     在System.Reactive.Concurrency.VirtualTimeSchedulerBase`2.AdvanceBy(TRelative time)     at UnitTestProject1.UnitTest1.d__6.MoveNext()in ....... \ UnitTest1.cs:line

失败似乎对所使用的IObservable的确切类型不敏感,但似乎依赖于Timeout选择器的存在。有趣的是,测试通常在超时导致火灾之前的虚拟时间失败。

运行与控制台应用程序相同的代码似乎也可以工作,虽然问题似乎很容易被扰乱(可能是竞争条件)所以这可能是一个红鲱鱼。

进一步的研究和评论意味着行为是由于当后台线程调度其超时操作时调度程序处于高级时发生的竞争条件

提前感谢您可以解决的任何问题...

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
        }
    }
}
}

1 个答案:

答案 0 :(得分:1)

stacktrace System.Reactive.Concurrency.VirtualTimeScheduler2.GetNext()中的这一行是一个线索,错误确实是你从两个线程访问调度程序( TestScheduler不是线程安全的当前有{{ 3}} TestScheduler从多个线程访问时。}

您的第一条评论可能是正确的:后台任务正在添加到调度程序,就像您的其他任务正在推进调度程序一样。

尝试在访问调度程序的语句周围添加一个锁,看看是否能解决您的问题。

当然,真正的修复方法是在设置完成后获得后台任务信号,并让测试任务在继续之前等待信号。因为即使TestScheduler没有它的bug,你的测试也有一个竞争条件,测试线程可以在后台线程订阅observable之前完成。