我试图将IEnumerable
变成IObservable
,以一秒钟的速度将其分块投放到其中。{/ p>
var spartans = Enumerable.Range(0, 300).ToObservable();
spartans
.Window(30)
.Zip(Observable.Timer(DateTimeOffset.Now, TimeSpan.FromMilliseconds(1000)), (x, _) => x)
.SelectMany(w => w)
.Subscribe(
n => Console.WriteLine("{0}", n),
() => Console.WriteLine("all end"));
使用此代码,唯一打印的是"所有结束"十秒钟后如果我删除了.Zip
,则整个序列立即打印,如果我删除.Window
和.SelectMany
,则整个序列每秒打印一个项目。如果我偷看"窗口"传递给SelectMany
的lambda中的observable,我可以看到它是空的。我的问题是,为什么?
答案 0 :(得分:2)
问题出现的原因是Window
如何处理计数 - 这个问题并不是特别直观!
如您所知,Window
提供了一串流。然而,通过计数,孩子们的流是温暖的" - 即当此流的观察者在其OnNext
处理程序中接收到新窗口时,它必须先将其订阅,然后再将其控制回可观察状态,否则事件将丢失。
Zip
没有"知道"它处理这种情况,并且没有机会在抓住每个子窗口之前订阅它们。
如果您删除Zip
,则会看到所有事件,因为SelectMany
会在收到所有子窗口时订阅它们。
最简单的解决方法是使用Buffer
代替Window
- 进行一次更改并使用您的代码。这是因为Buffer
与SelectMany
非常相似,通过这样做有效地保留了窗口:
Window(30).SelectMany(x => x.ToList())
元素不再是温暖的窗口,而是作为列表结晶,您的Zip
现在将按预期工作,以下SelectMany
将列表展平。
重要的是要注意这种方法将导致整个IEnumerable<T>
一次性运行。如果源可枚举应该被懒惰地评估(通常是可取的),那么你需要采用不同的方式。使用下游的observable控制上游的速度是棘手的。
让我们用辅助方法替换您的枚举,这样我们就可以看到每批30的评估时间:
static IEnumerable<int> Spartans()
{
for(int i = 0; i < 300; i++)
{
if(i % 30 == 0)
Console.WriteLine("30 More!");
yield return i;
}
}
并像这样使用它(使用Buffer
&#34;修复&#34;此处,但行为类似于Window
):
Spartans().ToObservable()
.Buffer(30)
.Zip(Observable.Timer(DateTimeOffset.Now,
TimeSpan.FromMilliseconds(1000)),
(x, _) => x)
.SelectMany(w => w)
.Subscribe(
n => Console.WriteLine("{0}", n),
() => Console.WriteLine("all end"));
然后你会看到这种输出演示了如何一次性消耗源可枚举:
30 More!
0
1
...miss a few...
29
30 More!
30 More!
30 More!
30 More!
30 More!
30 More!
30 More!
30 More!
30 More!
30
31
32
...etc...
要真正调整源代码,而不是直接使用ToObservable()
,您可以执行以下操作。请注意,Buffer
Spartans()
上的IEnumerable<T>
操作来自nuget包Ix-Main
- 由Rx团队添加,以便在IEnumerable<T>
monad上插入一些漏洞:< / p>
var spartans = Spartans().Buffer(30);
var pace = Observable.Timer(DateTimeOffset.Now, TimeSpan.FromMilliseconds(1000));
pace.Zip(spartans, (_,x) => x)
.SelectMany(x => x)
.Subscribe(
n => Console.WriteLine("{0}", n),
() => Console.WriteLine("all end"));
输出变得可能更为理想的延迟评估输出:
30 More!
0
1
2
...miss a few...
29
30 More!
30
31
32
...miss a few...
59
30 More!
60
61
62
...etc
答案 1 :(得分:0)
我不确定如何使用Window,但是这个:
var spartans = Enumerable.Range(0, 300).ToObservable();
spartans
.Select(x => Observable.Timer(TimeSpan.FromSeconds(1)).Select(_ => x))
.Merge(30);