想知道是否有人能想到这个用例的优雅解决方案:
我正在使用一个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);
}
答案 0 :(得分:3)
我一般都喜欢你正在做的事情,但我可以看到很多问题。
首先,您要将CreateSubscription
和GetStream
分为两种方法,并认为您将对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中进行本地化)。因此,我将CreateSubscription
和GetStream
合并为一个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。
如果您需要任何澄清,请告诉我。