2

昨天,我观看了编写您的第一个 Rx 应用程序(在第 9 频道)的截屏视频,其中 Wes Dyer 展示了如何使用响应式扩展 (Rx)实现 Drag 'n' Drop 。我仍然不明白的东西:

在截屏视频快结束时,韦斯·戴尔键入以下内容:

var q = from start in mouseDown
        from delta in mouseMove.StartsWith(start).Until(mouseUp)
                       .Let(mm => mm.Zip(mm.Skip(1), (prev, cur) =>
                           new { X = cur.X - prev.X, Y = cur.Y - prev.Y }))
        select delta;

简而言之,q是一个将鼠标移动坐标增量推送给其订阅者的 observable。

我不明白的是怎么mm.Zip(mm.Skip(1), ...)可能工作!?

据我所知,IObservable在这个意义上是不可枚举的IEnumerable。由于 的“拉”性质IEnumerable,它可以一次又一次地迭代,总是产生相同的项目。(至少所有表现良好的枚举都应该是这种情况。)IObservable工作方式不同。项目被推送给订阅者一次,就是这样。在上面的例子中,鼠标移动是单个事件,如果没有记录在内存中就不能重复。

那么,.Zip.Skip(1)可能的组合如何工作,因为他们正在处理的鼠标事件是单一的、不可重复的事件?这个操作不需要mm独立“查看”两次吗?


作为参考,以下是 的方法签名Observable.Zip

public static IObservable<TResult> Zip <TLeft, TRight, TResult>
(
    this IObservable<TLeft>       leftSource,     //  = mm
    IObservable<TRight>           rightSource,    //  = mm.Skip(1)
    Func<TLeft, TRight, TResult>  selector
)

PS:我刚刚看到运营商的另一个截屏视频Zip非常有见地。

4

3 回答 3

3

这个操作不需要mm独立“看”两次吗?

这实际上就是您问题的答案:您可以IObservable多次订阅相同的序列。

mm.Skip(1)订阅mm并将第一个值隐藏给它自己的订阅者。Zip 是 和 的订阅mm.Skip(1)mm。因为mm产生的值比 多一个mm.Skip(1),所以 Zip 一直在内部缓冲最后一个 mousemove 事件mm,以便将它与下一个未来的 mousemove 事件一起压缩。然后选择器函数可以选择两者之间的增量。

Observable.FromEvent您应该注意的另一件事是(这是您问题标题的真正答案) ,这IObservable是一个热门的observable ,因此不可重复。但是有一些的 Observables 实际上是可重复的,比如 Observable.Range(0,10)。在后一种情况下,每个订阅者将收到相同的 10 个事件,因为它们是为每个订阅者独立生成的。对于 mousemove 事件,情况并非如此(您不会从过去获得鼠标移动事件)。但是因为 Zip 同时订阅了左右序列,在这种情况下可能是相同的。

PS:您也可以创建一个热/不可重复IEnumerable:它不需要为每个枚举器返回相同的值。例如,您可以创建一个 IEnumerable 等待直到 mousemove 事件发生然后产生该事件。在这种情况下,枚举器总是会阻塞(糟糕的设计),但这是可能的。;)

于 2010-09-04T18:24:41.907 回答
2

啊哈!我在 PS 中提到的Zip截屏视频给了我一个重要的线索:Zip“记住”项目以说明项目可能比另一个更早到达一个可观察对象的事实。我会尝试回答我的问题,如果我错了,我希望有人能纠正我。

Zip将来自两个可观察序列的输入配对,如下所示(字母和数字是“事件”):

mm                        ----A---------B-------C------D-----------E----->
                              |         |       |      |           |
                              |         |       |      |           |
mm.Skip(1)                ----+---------1-------2------3-----------4----->
                              |         |       |      |           |
                              |         |       |      |           |
mm.Zip(mm.Skip(1), ...)   ----+--------A,1-----B,2----C,3---------D,4---->

它确实必须进行内部缓冲。在我发布的代码中,mm是真实的、“实时的”可观察的。mm.Skip(1)类似于从它派生的状态机。Alex Paven 的回答简要解释了这是如何工作的。

所以,mm.Zip(mm.Skip(1), ...)确实会看mm两次,一次是直接看,一次是通过Skip(n)过滤器。而且因为 observables 不是可重复的序列,所以它会进行内部缓冲,以解释一个序列比另一个序列更快产生项目的事实。

(我快速浏览了带有 .NET Reflector 的 Rx 源代码,确实Zip涉及到一个 .NET 文件Queue。)

于 2010-09-03T11:43:04.693 回答
1

项目被推送给订阅者一次,就是这样。

是的,一个项目被推送一次,但该项目是事件“序列”之一。序列仍然是序列。这就是 Skip 起作用的原因——它会跳过一个项目,然后在下一个项目到来时处理它(不跳过它)。

于 2010-09-03T11:10:26.267 回答