Observable.Window和.Zip不能像我期望的那样运作

时间:2015-02-21 14:50:10

标签: c# system.reactive

我试图将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,我可以看到它是空的。我的问题是,为什么?

2 个答案:

答案 0 :(得分:2)

问题出现的原因是Window如何处理计数 - 这个问题并不是特别直观!

如您所知,Window提供了一串流。然而,通过计数,孩子们的流是温暖的" - 即当此流的观察者在其OnNext处理程序中接收到新窗口时,它必须先将其订阅,然后再将其控制回可观察状态,否则事件将丢失。

Zip没有"知道"它处理这种情况,并且没有机会在抓住每个子窗口之前订阅它们。

如果您删除Zip,则会看到所有事件,因为SelectMany 会在收到所有子窗口时订阅它们。

最简单的解决方法是使用Buffer代替Window - 进行一次更改并使用您的代码。这是因为BufferSelectMany非常相似,通过这样做有效地保留了窗口:

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);