RX:如何连接快照流和更新流?

时间:2013-06-14 11:55:18

标签: c# repository-pattern system.reactive

我一直在尝试创建一个observable,它从存储库缓存中流式传输世界状态(快照),然后从单独的Feed中进行实时更新。问题是快照调用是阻塞的,因此在此期间必须缓冲更新。

这就是我想出来的,有点简化了。 GetStream()方法是我关注的方法。我想知道是否有更优雅的解决方案。假设GetDataFeed()整天都会对缓存进行更新。

private static readonly IConnectableObservable<long> _updateStream;

public static Constructor()
{
      _updateStream = GetDataFeed().Publish();
      _updateStream.Connect();
}

static void Main(string[] args)
{
      _updateStream.Subscribe(Console.WriteLine);
      Console.ReadLine();
      GetStream().Subscribe(l => Console.WriteLine("Stream: " + l));
      Console.ReadLine();
}

public static IObservable<long> GetStream()
{
      return Observable.Create<long>(observer =>
            {
                  var bufferedStream = new ReplaySubject<long>();
                  _updateStream.Subscribe(bufferedStream);
                  var data = GetSnapshot();
                  // This returns the ticks from GetSnapshot
                  //  followed by the buffered ticks from _updateStream
                  //  followed by any subsequent ticks from _updateStream
                  data.ToObservable().Concat(bufferedStream).Subscribe(observer);

                  return Disposable.Empty;
            });
}

private static IObservable<long> GetDataFeed()
{
      var feed = Observable.Interval(TimeSpan.FromSeconds(1));
      return Observable.Create<long>(observer =>
      {
            feed.Subscribe(observer);
            return Disposable.Empty;
      });
}

流行观点反对主体,因为它们不是“功能性的”,但如果没有ReplaySubject,我找不到这样做的方法。热观察上的重放过滤器不起作用,因为它会重放所有内容(可能需要一整天的陈旧更新)。

我也很关心比赛条件。有没有办法保证某种排序,如果在快照之前缓冲早期更新?可以用其他RX操作员更安全,更优雅地完成整个事情吗?

感谢。

- 将会

1 个答案:

答案 0 :(得分:4)

无论您使用ReplaySubject还是Replay功能,都没有任何区别。 Replay在引擎盖下使用ReplaySubject。我还会注意到你正在泄漏疯狂的订阅,这会导致资源泄漏。此外,您对重放缓冲区的大小没有限制。如果您整天观察可观察量,那么重放缓冲区将继续增长和增长。你应该对它加以限制以防止这种情况发生。

以下是GetStream的更新版本。在这个版本中,我采用简单的方法将Replay限制为最近1分钟的数据。这假设GetData将始终完成并且观察者将在1分钟内观察到结果。您的里程可能会有所不同,您可以改进此计划。但至少就是这样,当你整天看着这个可观察的时候,那个缓冲区不会无限制地增长,而且只会包含一分钟的更新。

public static IObservable<long> GetStream()
{
    return Observable.Create<long>(observer =>
    {
        var updateStreamSubscription = new SingleAssignmentDisposable();
        var sequenceDisposable = new SingleAssignmentDisposable();
        var subscriptions = new CompositeDisposable(updateStreamDisposable, sequenceDisposable);

        // start buffering the updates
        var bufferedStream = _updateStream.Replay(TimeSpan.FromMinutes(1));
        updateStreamSubscription.Disposable = bufferedStream.Connect();

        // now retrieve the initial snapshot data
        var data = GetSnapshot();

        // subscribe to the snapshot followed by the buffered data
        sequenceDisposable.Disposable = data.ToObservable().Concat(bufferedStream).subscribe(observer);

        // return the composite disposable which will unsubscribe when the observer wishes
        return subscriptions;
    });
}

关于竞争条件和过滤掉“旧”更新的问题...如果您的快照数据包含某种版本信息,并且您的更新流也提供版本信息,那么您可以有效地测量返回的最新版本您的快照查询,然后筛选缓冲的流以忽略旧版本的值。这是一个粗略的例子:

public static IObservable<long> GetStream()
{
    return Observable.Create<long>(observer =>
    {
        var updateStreamSubscription = new SingleAssignmentDisposable();
        var sequenceDisposable = new SingleAssignmentDisposable();
        var subscriptions = new CompositeDisposable(updateStreamDisposable, sequenceDisposable);

        // start buffering the updates
        var bufferedStream = _updateStream.Replay(TimeSpan.FromMinutes(1));
        updateStreamSubscription.Disposable = bufferedStream.Connect();

        // now retrieve the initial snapshot data
        var data = GetSnapshot();

        var snapshotVersion = data.Length > 0 ? data[data.Length - 1].Version : 0;
        var filteredUpdates = bufferedStream.Where(update => update.Version > snapshotVersion);

        // subscribe to the snapshot followed by the buffered data
        sequenceDisposable.Disposable = data.ToObservable().Concat(filteredUpdates).subscribe(observer);

        // return the composite disposable which will unsubscribe when the observer wishes
        return subscriptions;
    });
}

在将实时更新与存储的快照合并时,我已成功使用此模式。我还没有找到一个优雅的Rx运算符,它已经没有任何竞争条件。但上述方法可能会变成这样。 :)

编辑:注意我在上面的例子中遗漏了错误处理。理论上,对GetSnapshot的调用可能会失败,并且您将订阅泄漏到更新流。我建议在CompositeDisposable块中的try/catch声明之后包装所有内容,并在catch处理程序中,确保在重新抛出异常之前调用subscriptions.Dispose()