Rx.Observable.reduce的意外行为takeUntil concat

时间:2016-08-24 10:45:56

标签: system.reactive rxjs5

我对以下代码的行为感到惊讶(请参阅https://plnkr.co/edit/OVc26DmXpvXqSOJsQAoh?p=preview):

  let empty = Observable.empty();
  let source = Observable.range(1, 5)
    .map(i =>
      Observable.timer(i * 2000, 1000).map(x => "source " + i + ": " + x).take(10))
    .reduce((s1, s2) => s1.takeUntil(s2).concat(s2), empty)
    .mergeAll();

   var subscription = source.subscribe(
    function (x) {
        console.log('Next: ' + x);
    },
    function (err) {
        console.log('Error: ' + err);   
    },
    function () {
        console.log('Completed');   
    });

产量

Next: source 1: 0
Next: source 1: 1

---这里有一个长时间的停顿---

Next: source 5: 0
Next: source 5: 1
Next: source 5: 2
Next: source 5: 3
Next: source 5: 4
Next: source 5: 5
Next: source 5: 6
Next: source 5: 7
Next: source 5: 8
Next: source 5: 9
Completed

但我希望看到所有序列出现在中间。出了什么问题?

编辑:

请注意,使用share()并不总是能够治愈它。此代码失败:

   let originalSequence = Observable.timer(0, 1000).take(10).share();

   let empty = Observable.empty();
     let source = Observable.range(1, 5)
      .map(i =>
      originalSequence.delay(i * 2000).map(x => "source " + i + ": " + x))
    .reduce((s1, s2) => s1.takeUntil(s2).concat(s2), empty)
    .mergeAll(); 

并且此代码按预期工作,我不明白为什么

 let empty = Observable.empty();
     let source = Observable.range(1, 5)
      .map(i =>
      Observable.timer(i * 2000, 1000).map(x => "source " + i + ": " + x).take(10).share())
    .reduce((s1, s2) => s1.takeUntil(s2).concat(s2), empty)
    .mergeAll();

编辑2:

C#版本也有一种我不会期望的行为,但同时表现为不同

using System;
using System.Linq;
using System.Reactive.Linq;
using System.Threading;

namespace RxScanProblem
{
    class Program
    {
        static void Main(string[] args)
        {
            var originalSequence = Observable.Timer(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1)).Take(10).Select(i => (long)i).Publish();

            var empty = Observable.Empty<string>();
            var source = Observable.Range(1, 5)
             .Select(i => originalSequence.Delay(TimeSpan.FromSeconds(2 * i)).Select(x => "source " + i + ": " + x))
             .Aggregate(empty, (s1, s2) => s1.TakeUntil(s2).Concat(s2))
             .SelectMany(x => x);

            source.Subscribe(
                s => Console.WriteLine("Next: " + s),
                ex => Console.WriteLine("Error: " + ex.Message),
                () => Console.WriteLine("Completed"));

            originalSequence.Connect();

            // Dirty, I know
            Thread.Sleep(20000);
        }
    }
}

收益率(有一些延迟)

Next: source 1: 0
Next: source 1: 1
Next: source 1: 2

编辑3

同样switch()的行为并不像我期望的那样!

   let empty = Observable.empty();
     let source = Observable.range(1, 5)
      .map(i => Observable.timer(i * 2000, 1000).map(x => "source " + i + ": " + x).take(10))
      .switch();

产量

Next: source 5: 0
Next: source 5: 1
Next: source 5: 2
Next: source 5: 3
Next: source 5: 4
Next: source 5: 5
Next: source 5: 6
Next: source 5: 7
Next: source 5: 8
Next: source 5: 9

C#的相同(!)行为

   var originalSequence = Observable.Timer(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1)).Take(10).Select(i => (long)i).Publish();

    var empty = Observable.Empty<string>();
    var source = Observable.Range(1, 5)
     .Select(i => originalSequence.Delay(TimeSpan.FromSeconds(2 * i)).Select(x => "source " + i + ": " + x))
     .Switch();

2 个答案:

答案 0 :(得分:2)

让我们看看你的代码做了什么。

  let source = Observable.range(1, 5)
    .map(i =>
      Observable.timer(i * 2000, 1000).map(x => "source " + i + ": " + x).take(10))
    .reduce((s1, s2) => s1.takeUntil(s2).concat(s2), empty)
    .mergeAll();

第一张地图会将{1,2,3,4,5}转换为

s1 = Observable.timer(1 * 2000, 1000).map(x => "source 1: " + x).take(10));
s2 = Observable.timer(2 * 2000, 1000).map(x => "source 2: " + x).take(10));
s3 = Observable.timer(3 * 2000, 1000).map(x => "source 3: " + x).take(10));
s4 = Observable.timer(4 * 2000, 1000).map(x => "source 4: " + x).take(10));
s5 = Observable.timer(5 * 2000, 1000).map(x => "source 5: " + x).take(10));

接下来,reduce会将它们粘合在一起,如下所示:

s1.takeUntil(s2).concat(s2)
   .takeUntil(s3).concat(s3)
   .takeUntil(s4).concat(s4)
   .takeUntil(s5).concat(s5)

现在让我们写一个小大理石来展示所有这些流将会产生什么:

s1               --0123456789
s2               ----0123456789
s3               ------0123456789
s4               --------0123456789
s5               ----------0123456789
s1.takeUntil(s2) --01|
  .concat(s2)    --01----0123456789
  takeUntil(s3)  --01--|
  .concat(s3)    --01--------0123456789
  takeUntil(s4)  --01----|
  .concat(s4)    --01------------0123456789
  takeUntil(s5)  --01------|
  .concat(s5)    --01----------------0123456789

现在,如果您使用share(),则可以有效地发布源代码。发布意味着您同时向所有订阅者进行多播。如果有2个用户,即使一个用户晚到达另一个用户,这种情况也会很好,源将继续在第二个用户的中间流。当第一个用户在第二个用户到达之前断开连接时,情为了保留资源,share()将断开源,并在以后重新订阅。鉴于你从冷观察开始,这意味着他们将在开始时再次开始,等待很长时间。

由于您使用的是.takeUntil(s2).concat(s2),因此在您再次订阅s2之前,您实际上会取消订阅s2。毕竟concat不会连接,直到它从completed收到takeUntiltakeUntil不会发出completed,直到s2已经屈服。如果s2收益,则takeUntil会在转发completed下游之前立即取消订阅。这意味着s2将暂时没有订户,并且将重置源。

您可能期望s2在整个时间内保持联系,并且会继续在后台运行。如果您使用了由活动源而非冷可观察的热槽share()生成的热观察值,则此方法可行。

我不会在switch()详细说明,因为我相信你已经理解了那里的问题:当下一个来源到达时,它会断开先前的来源,而不是下一个来源。

你可以做的是写自己的&#39; switchOnYield&#39;

source.publish(src => src
  .flatMap(inner1 => 
    inner1.takeUntil(src.flatMap(inner2 => inner2.take(1)))
  ))

这样做是将源中的所有源合并在一起,但是在后面添加takeUntil所有来源。如果任何后来的来源产生,第一批将被取消订阅。这是有效的,因为第一次生成src.flatMap(inner1将会运行。第二次收益时,src.flatMap(inner2会将后一个来源中的任何项目合并到takeUntil运算符中。

demo here

答案 1 :(得分:0)

我强烈建议您发布预期或期望的输出。它不清楚你想要什么。对于C#示例,将源代码更改为以下内容会让您更接近(我认为,再次,我不确定):

var source = Observable.Range(1, 5)
    .Select(i => originalSequence
        .Delay(TimeSpan.FromSeconds(2 * i))
        .Select(x => "source " + i + ": " + x)
    )
    //making sure s2 is shared properly, thus concated properly
    .Aggregate(empty, (s1, s2) => s2.Publish( _s2 => s1
        .TakeUntil(_s2)
        .Concat(_s2)
    ))
    .SelectMany(x => x);

产生以下输出:

Next: source 1: 0
Next: source 1: 1
Next: source 2: 1
Next: source 3: 1
Next: source 4: 1
Next: source 5: 1
Next: source 5: 2
Next: source 5: 3
Next: source 5: 4
Next: source 5: 5
Next: source 5: 6
Next: source 5: 7
Next: source 5: 8
Next: source 5: 9
Completed

这对我有意义。如果您可以发布所需的输出,我会帮助您实现目标。