这两个可观察的操作是否等效?

时间:2014-03-05 14:44:15

标签: f# system.reactive

我不确定为什么,但出于某种原因,当使用通过concat创建的observable时,我将始终获得从列表中推送的所有值(按预期工作)。与正常订阅一样,似乎某些值永远不会出现给那些订阅了observable的人(仅在某些条件下)。

这是我正在使用的两个案例。任何人都可以尝试解释为什么在某些情况下订阅第二个版本并不是所有的值都被收到?它们不相同吗?这里的目的是回放流。有哪些原因可以解释为什么案例2失败而案例1没有。

此处重播只是正在进行的流的列表。

案例1。

let observable = 
        Observable.Create(fun (o:IObserver<'a>) ->
                        let next b =
                            for v in replay do
                                o.OnNext(v.Head)
                            o.OnNext(b)
                            o.OnCompleted()
                        someOtherObs.Subscribe(next, o.OnError, o.OnCompleted))
let toReturn = observable.Concat(someOtherObs).Publish().RefCount()

案例2。

let toReturn = 
    Observable.Create(fun (o:IObserver<'a>) ->
        for v in replay do
            o.OnNext(v.Head)
        someOtherObs.Subscribe(o)
    ).Publish().RefCount()

1 个答案:

答案 0 :(得分:4)

买者!我没有足够频繁地使用F#来熟悉语法,但我想我知道发生了什么。

也就是说,这两种情况对我来说都很奇怪,这在很大程度上取决于someOtherObs的实现方式,以及(在线程方面)运行的情况。

案例1分析

您将concat应用于源流,该源流看起来像这样:

  • 它订阅someOtherObs,并在响应第一个事件(a)时将重播元素推送给观察者。
  • 然后它将事件(a)发送给观察者。
  • 然后就完成了。此时流已完成,不再发送任何事件。
  • 如果someOtherObs为空或只有一个错误,则会将其传播给观察者。

现在,当此流完成时,someOtherObs将连接到它。现在发生的事情有点不可预测 - 如果someOtherObs是冷的,那么第一次事件将被第二次发送,如果someOtherObs很热,那么第一次事件不会被重新发送,但是有一个潜在的竞争条件,其余的事件将围绕下一步取决于someOtherObs的实现方式。如果事情很热,你很容易错过。

案例2分析

你重播所有重播事件,然后发送someOtherObs的所有事件 - 但是如果someOtherObs很热,那么再次出现竞争条件,因为你只是在推送重播后订阅,所以可能会错过一些事件。

评论

在任何一种情况下,对我来说似乎都很混乱。

这看起来像是尝试合并世界状态(sotw)和直播。在这种情况下,您需要先订阅实时流,然后在获取并推送sotw事件时缓存任何事件。一旦推出了sotw,你就可以推送缓存的事件 - 小心去掉可能在sotw中读取的事件 - 直到你被现场直播,然后你可以直接传递直播事件。

你可以经常使用简单的实现来实现在实时流订阅的OnNext处理程序中刷新实时缓存,在刷新时有效地阻止源 - 但是你冒着对实时源施加过多背压的风险你有悠久的历史和/或快速移动的直播。

您需要考虑的一些考虑因素将有助于您走上正确的道路。

作为参考,这里是一个非常天真和简单的 C#实现,我在LINQPad中用rx-main nuget包编译。我过去所做的生产就绪实施可能变得非常复杂:

void Main()
{
    // asynchronously produce a list from 1 to 10
    Func<Task<List<int>>> sotw =
        () => Task<List<int>>.Run(() => Enumerable.Range(1, 10).ToList());

    // a stream of 5 to 15
    var live = Observable.Range(5, 10);

    // outputs 1 to 15
    live.MergeSotwWithLive(sotw).Subscribe(Console.WriteLine);
}

// Define other methods and classes here
public static class ObservableExtensions
{
    public static IObservable<TSource> MergeSotwWithLive<TSource>(
        this IObservable<TSource> live,
        Func<Task<List<TSource>>> sotwFactory)
    {
        return Observable.Create<TSource>(async o =>
        {       
            // Naïve indefinite caching, no error checking anywhere             
            var liveReplay = new ReplaySubject<TSource>();
            live.Subscribe(liveReplay);
            // No error checking, no timeout, no cancellation support
            var sotw = await sotwFactory();
            foreach(var evt in sotw)
            {
                o.OnNext(evt);
            }                               

            // note naive disposal
            // and extremely naive de-duping (it really needs to compare
            // on some unique id)
            // we are only supporting disposal once the sotw is sent            
            return liveReplay.Where(evt => !sotw.Any(s => s.Equals(evt)))
                    .Subscribe(o);                  
        });
    }
}