我对以下代码的行为感到惊讶(请参阅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();
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
同样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();
答案 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
收到takeUntil
而takeUntil
不会发出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
运算符中。
答案 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
这对我有意义。如果您可以发布所需的输出,我会帮助您实现目标。