Reactive Extensions是否评估了太多次?

时间:2012-09-07 13:35:31

标签: c# system.reactive

Reactive Extensions应该评估其各种运营商的次数?

我有以下测试代码:

var seconds = Observable
    .Interval(TimeSpan.FromSeconds(5))
    .Do(_ => Console.WriteLine("{0} Generated Data", DateTime.Now.ToLongTimeString()));

var split = seconds
    .Do(_ => Console.WriteLine("{0}  Split/Branch received Data", DateTime.Now.ToLongTimeString()));

var merged = seconds
    .Merge(split)
    .Do(_ => Console.WriteLine("{0}   Received Merged data", DateTime.Now.ToLongTimeString()));

var pipeline = merged.Subscribe();

我希望每五秒钟写一次“生成数据”。然后,它将该数据移交给写入“拆分/分支接收数据”的“拆分”流,以及写入“收到合并数据”的“合并”流。最后,因为'merged'流也是从'split'流接收的,所以它第二次接收数据并第二次写入“Received Merged data”。 (它写入其中一些的顺序并不是特别相关)

但我得到的输出是:

8:29:56 AM Generated Data
8:29:56 AM Generated Data
8:29:56 AM  Split/Branch received Data
8:29:56 AM   Received Merged data
8:29:56 AM   Received Merged data
8:30:01 AM Generated Data
8:30:01 AM Generated Data
8:30:01 AM  Split/Branch received Data
8:30:01 AM   Received Merged data
8:30:01 AM   Received Merged data

两次写“Generaged Data”。根据我的理解,订阅“秒”IObservable的下游观察者的数量不应该影响“生成数据”写入的次数(应该是ONCE),但确实如此。为什么呢?

注意我在.Net Framework 3.5环境中使用反应式扩展的稳定版本v1.0 SP1。

4 个答案:

答案 0 :(得分:2)

据推测,他们选择这种方法允许每个订阅者以与初始订阅相同的间隔获取其值。考虑您的备用Interval如何工作:

0s - First subscriber subscribes
5s - Value: 0
8s - Second subscriber subscribes
10s - Value: 1
15s - Value: 2
17s - Unsubscribe both

你最终得到的是这样的:

First  -----0----1----2-|
Second         --1----2-|

在这种情况下,两名观察者的结果显着,具体取决于是否已连接任何其他观察者。在实施时,Interval为每个订阅者提供相同的体验,无论订单或过去的订阅者如何。

所有这一切,您可以在创建Interval可观察时添加.Publish().RefCount(),将seconds“转换”为您描述的行为。

答案 1 :(得分:1)

虽然看起来有时候如果序列在每一步都被多播,但是如果它是这样的话,它就不会让你拥有Rx允许的丰富成分。

以另一种方式思考, IObservableIEnumerable的基于推送的双重身份。 IEnumerable具有延迟评估的属性 - 在您开始移动Enumerator之前,不会计算这些值。 Rx序列是懒惰地组成的,最后一个Subscribe()(Forservable等效于For-Each)实现了序列。

通过这种方式,您只需从最后一个阶段取消订阅就可以在所有阶段停止管道,让您在不经历管理个人订阅的噩梦的情况下解雇并忘记行为。

答案 2 :(得分:0)

在相关的说明中,这里有一个脑力激荡器,用阿兹蒂的懒惰评估可枚举序列来比喻:

private static Random s_rand = new Random();

public static IEnumerable<int> Rand()
{
    while (true)
        yield return s_rand.Next();
}

public static void Main()
{
    var xs = Rand();

    var res = xs.Zip(xs, (l, r) => l == r).All(b => b);

    Console.WriteLine(res);
}

如果你自己拉随机序列,你是否期望所有元素对都相同(即导致上面的代码永远运行)?或者,您是否希望代码因某种原因终止并打印错误?

(创建类似的可观察代码留给读者练习。)

答案 3 :(得分:0)

从面向对象的角度来看,将流定义为定义Observables / Enumerables的接口是正常的。如果你可以忽略这样一个事实,那就是在枚举器上有一个名为Reset的方便方法 - 在功能上,Enumerables是f -> g -> value?。可枚举本质上是一个函数,你调用它来获取枚举器,它实际上是一个你一直调用的函数,直到没有更多的值要返回。

类似地,Observable被简单地定义为f(g) -> g(h) -> h(value?) - 它是一个函数,当你有一个值时,你提供你想要调用的函数。

这就是为什么将可枚举或可观察的东西描述为除了以某种方式定义的一组函数以便它们可以被组合之外的任何东西都没有意义 - 合同是为了确保组合计算的能力。

它们是实时的,缓存的还是懒惰的是可以在其他地方抽象的实现细节 - 虽然我当然不同意这些细节是重要的,但更重要的是关注它的功能性质。

作为数据库查询或目录列表的序列具有与预先计算的值集(如数组)相同的IEnumerable接口。这取决于最终消耗序列的代码以进行区分。如果您已经习惯了构建高阶函数的方法,那么您会发现使用Rx或Ix建模问题更容易。