重放() - 类似功能,但能够取代陈旧的价值?

时间:2015-08-21 10:29:52

标签: c# system.reactive

想知道是否有人能想到这个用例的优雅解决方案:

我正在使用一个observable(类型为TEntity的IObservable),它为我提供了一组实体。如果更新了这些实体中的任何一个,则observable的提供者将按下更新的实体。

我在这个流上使用Replay(),这样我只需要订阅一次底层流,这样后期订阅者就可以看到所有的值。问题是这里存在内存泄漏的可能性,因为Replay()将保留它看到的所有更新,而我所需要的只是每个实体的最新更新。

我可以用Scan()替换Replay(),它允许我只维护最新的更新,但是我必须推出到目前为止观察到的所有更新的字典,而不仅仅是特定的实体已经改变了。

我能想到的唯一解决方案是使用上面的Scan(),但在Scan()实现中,我会将所有更新推送到Subject中。我将公开的IObservable的订阅者将收到存储在Scan()字典中的Snapshot和任何更新的合并,如下所示:

private Subject<Entity> _updateSubject = new Subject<Entity>();

private IObservable<Dictionary<string, Entity>> _subscriptionStream;

//called once on initialisation
private void CreateSubscription()
{
    IObservable<Entity> source = GetSomeLongRunningSubscriptionStream();

    _subscriptionStream = source
    .Scan(new Dictionary<string, Entity>(), (accumulator,update) =>
    {
        accumulator[update.ID] = update;

        _updateSubject.OnNext(update);

        return accumulator;
    })
    .Replay(1);
}

//called each time a consumer wants access to the stream
public IObservable<Entity> GetStream()
{
    return _subscriptionStream.Take(1).SelectMany(x => x).Select(x => x.Value)
    .Merge(_updateSubject.AsObservable());
}

任何人都可以想到一个更优雅的解决方案,将状态保存在单个流中而不是诉诸主题吗?

由于

**************编辑**************

根据我的评论,我已经找到了类似的东西。让我知道你的想法

//called once on initialisation
private void CreateSubscription()
        {
            _baseSubscriptionObservable = GetSomeLongRunningSubscriptionStream ().Publish();

            _snapshotObservable = _baseSubscriptionObservable
                .Scan(new Dictionary<string,Entity>(), (accumulator, update) =>
                    {
                        accumulator[update.ID] = update;

                        return accumulator;
                    })
                .StartWith(new Dictionary<string, Entity>())
                .Replay(1);

            _baseSubscriptionObservable.Connect ();
            _snapshotObservable.Connect ();
        }

public IObservable<Entity> GetStream()
        {
            return _snapshotObservable.Take (1).Select (x => x.Values).SelectMany (x => x)
                .Merge (_baseSubscriptionObservable);
        }

1 个答案:

答案 0 :(得分:3)

我一般都喜欢你正在做的事情,但我可以看到很多问题。

首先,您要将CreateSubscriptionGetStream分为两种方法,并认为您将对GetSomeLongRunningSubscriptionStream()流进行一次基础订阅。不幸的是,在这种情况下,无论您获得最终可观察量的订阅量为.Replay(1),您都会收到IConnectableObservable<>,而您需要致电.Connect()才能开始订阅。价值流动。

接下来就是您要使用最新值更新累加器,然后在GetStream中添加最新值以及合并到累加器的展平流中。您每次都会返回两次最新值。

以下是我建议你这样做的方法:

private IObservable<IList<Timestamped<Entity>>> GetStream()
{
    return
        Observable
            .Create<IList<Timestamped<Entity>>>(o =>
                GetSomeLongRunningSubscriptionStream()
                    .Timestamp()
                    .Scan(
                        new Dictionary<string, Timestamped<Entity>>(),
                        (accumulator, update) =>
                        {
                            accumulator[update.Value.ID] = update;
                            return accumulator;
                        })
                    .Select(x => x.Select(y => y.Value).ToList())
                    .Replay(1)
                    .RefCount()
                    .Subscribe(o));
}

使用Rx时,几乎总是最好避免任何状态(不能在observable中进行本地化)。因此,我将CreateSubscriptionGetStream合并为一个GetStream方法,并将整个observable封装到Observable.Create

为了避免两次推出值,并且为了便于您了解最新更新的内容,我已添加了对.Timestamp()的调用,以便在最新时间内返回Entity

我已将.Scan(...)与字典保持在一起,但它现在是Dictionary<string, Timestamped<Entity>>

对于每个添加/更新的值,我然后展平字典并将基础值作为列表返回。此时,您可以订购列表,以确保最新值是第一个或最后一个,以满足您的需求。

然后我使用.Replay(1).RefCount()组合将IConnectableObservable<>返回的.Replay(1)变回IObservable<>,并了解您已经知道了在所有订户处置时处置基础订阅。这可能是您查询中最重要的部分。它应该这样做。这是确保您避免内存泄漏的Rx方法。

如果您迫切需要保持底层连接打开,那么您需要将所有代码封装在实现IDisposable的类中,以清理您需要的.Connect()

这样的事情:

public class EntityStream : IDisposable 
{
    private IDisposable _connection = null;

    public EntityStream(IObservable<Entity> someLongRunningSubscriptionStream)
    {
        _stream =
            someLongRunningSubscriptionStream
            .Timestamp()
            .Scan(
                new Dictionary<string, Timestamped<Entity>>(),
                (accumulator, update) =>
                {
                    accumulator[update.Value.ID] = update;
                    return accumulator;
                })
            .Select(x => x.Select(y => y.Value).ToList())
            .Replay(1);

        _connection = _stream.Connect();
    }

    private IConnectableObservable<IList<Timestamped<Entity>>> _stream = null;

    public IObservable<IList<Timestamped<Entity>>> GetStream()
    {
        return _stream.AsObservable();
    }

    public void Dispose()
    {
    if (_connection != null)
    {
        _connection.Dispose();
        _connection = null;
    }
    }
}

我很少这样做。我会彻底推荐第一种方法。你必须在必要时混合使用OOP和Rx。

如果您需要任何澄清,请告诉我。