为什么我用Repeat()获得非确定性结果

时间:2011-11-12 11:18:16

标签: c# system.reactive reactive-programming

我正在努力扩展我对Rx的了解。所以我只是在玩溪流,试着让它们像我期望的那样表现。

虽然我已经读过bevor,但是Repeat()操作符在实践中有困难,因为你可能会在OnCompleted和重新订阅之间丢失通知,我无法弄清楚为什么会发生以下情况。

        var subject = new Subject<string>();

        var my = subject
            .Take(1)
            .Merge(Observable.Empty<string>().Delay(TimeSpan.FromMilliseconds(2000)))
            .Repeat();
        my.Subscribe(Console.WriteLine);

        var stopwatch = new Stopwatch();
        stopwatch.Start();
        Scheduler.ThreadPool.Schedule(TimeSpan.FromSeconds(1), () => subject.OnNext("1 at " + stopwatch.ElapsedMilliseconds));
        Scheduler.ThreadPool.Schedule(TimeSpan.FromSeconds(2), () => subject.OnNext("2 at " + stopwatch.ElapsedMilliseconds));
        Scheduler.ThreadPool.Schedule(TimeSpan.FromSeconds(3), () => subject.OnNext("3 at " + stopwatch.ElapsedMilliseconds));
        Scheduler.ThreadPool.Schedule(TimeSpan.FromSeconds(4), () => subject.OnNext("4 at " + stopwatch.ElapsedMilliseconds));
        Scheduler.ThreadPool.Schedule(TimeSpan.FromSeconds(5), () => subject.OnNext("5 at " + stopwatch.ElapsedMilliseconds));
        Scheduler.ThreadPool.Schedule(TimeSpan.FromSeconds(6), () => subject.OnNext("6 at " + stopwatch.ElapsedMilliseconds));

        Console.ReadLine();

当我运行这个例子时,结果完全不确定:

结果1:

1 at 1006
3 at 3007
5 at 4995

它遗漏了2和4是好的,但即使在这个结果中也存在一些奇怪之处,因为实际上在3和5之间并没有真正的2秒差距。

然而,结果可能更糟。见这个:

1 at 1003
2 at 2003
4 at 4005
6 at 6004

1到2之间没有2秒的差距。这恰好是一秒钟。他为什么不把它遗弃?

如果有人能为我澄清事情,我会非常高兴!

修改

我刚注意到这可能是合并错了。如果我将我的查询重构为Concat,事情似乎应该发生:

        var my = subject
            .Take(1)
            .Concat(Observable.Empty<string>().Delay(TimeSpan.FromMilliseconds(2000)))
            .Repeat();

2 个答案:

答案 0 :(得分:1)

Windows(和其他桌面操作系统)不是运行时操作系统,所以你不能相信它的定时器精确到毫秒。特别是如果你有更多的计时器,这可能导致非确定性行为,这正是你的情况。

这就是原始序列的工作原理:

  • 时间~0
    • Take(1)订阅了subject
    • 延迟空观察的计时器开始滴答作响
  • 时间~1
    • 1添加到subject,写出1;在此之后,没有人再订阅subject
  • 时间~2
    • 延迟空observable的计时器用完了。因此,Take(1)再次订阅subject,另一个计时器用于延迟空的可观察开始
    • 大约在同一时间,2被添加到subject

由于时间上的细微差别,这两个动作的时间约为2可以按任何顺序发生。并且顺序很重要,在Take()重新订阅之前或之前添加2。因此,2可以写出或不写出。

如果您想要的是这样的序列:

  1. 等待第一件商品并将其退回
  2. 等待两秒钟
  3. 等待第二项并将其返回(忽略在两秒钟等待期间添加的任何项目)
  4. ...
  5. 然后我认为编辑中的代码是正确的。

    但这绝不能保证确定性结果。在我的计算机上,如果我将延迟的空可观察量的等待时间更改为1960毫秒,则在使用Concat()时会得到不确定的结果。

答案 1 :(得分:0)

只是为了晚会加入我的2c;如果您正在寻找测试的确定性,那么您应该使用TestScheduler而不是真正的ThreadPool / TaskPool / NewThread调度程序。纯粹是因为Svick指出的原因(操作系统调度摇摆不定)。