Rx.NET:按顺序组合可观察量

时间:2018-05-11 18:51:07

标签: c# observable system.reactive

我有2 IConnectableObservable s,其中一个正在重播旧的历史消息,另一个正在发出新的当前值:

HistoricObservable: - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - ...
CurrentObservable:    - - - - - 5 - 6 - 7 - 8 - 9 - 10 - ...

如何将它们合并到一个observable中,以便从两个observable中获取完整(正确)序列,但是一旦我开始从CurrentObservable发出值,就会删除订阅并在HistoricObservable订阅上调用Dispose。

MergedObservable: - 1 - 2 - 3 - 4 - 56 - 7 - 8 - 9 - 10 - ...

我的消息由 Guid 标识,因此解决方案只能使用Equal进行比较,并且不能依赖于除了每个消息之外的任何排序。可观测量。

简而言之,我希望填充方法:

public static IObservable<T> MergeObservables<T>(
    IObservable<T> historicObservable,
    IObservable<T> currentObservable)
    where T : IEquatable<T>
{
    throw new NotImplementedException();
}

MergedObservable应该继续从HistoricObservable中发出值而不等待CurrentObservable的第一个值,如果先前已经发出了CurrentObservable的第一个值,那么MergedObservable应该跳过已经发出的CurrentObservable中的任何值,将订阅处置为HistoricObservable ,并开始从CurrentObservable获取所有新值。我也不想在CurrentObservable发出第一个对象时立即切换,直到我到达HistoricObservable中的那个点,所以我一直很难尝试使用TakeWhile / TakeUntil。我在使用CombineLatest方面取得了一些小小的成功来保存状态,但我认为这可能是一种更好的方式。

测试用例

对于以下测试用例,假设每条消息由GUID表示如下:

A = E021ED8F-F0B7-44A1-B099-9878C6400F34
B = 1139570D-8465-4D7D-982F-E83A183619DE
C = 0AA2422E-19D9-49A7-9E8C-C9333FC46C46
D = F77D0714-2A02-4154-A44C-E593FFC16E3F
E = 14570189-4AAD-4D60-8780-BCDC1D23273D
F = B42983F0-5161-4165-A2F7-074698ECCE77
G = D2506881-F8AB-447F-96FA-896AEAAD1D0A
H = 3063CB7F-CD25-4287-85C3-67C609FA5679
I = 91200C69-CC59-4488-9FBA-AD2D181FD276
J = 2BEA364E-BE86-48FF-941C-4894CEF7A257
K = 67375907-8587-4D77-9C58-3E3254666303
L = C37C2259-C81A-4BC6-BF02-C96A34011479
M = E6F709BE-8910-42AD-A100-2801697496B0
N = 8741D0BB-EDA9-4735-BBAF-CE95629E880D

1)如果历史的observable永远不会赶上当前的observable,那么合并的observable永远不会从当前的observable中发出任何东西

Historic: - A - B - C - D - E - F - G - H|
Current:    - - - - - - - - - - - - - - - I - J - K - L - M - N|
Merged:   - A - B - C - D - E - F - G - H|

2)一旦历史的observable达到当前observable发出的第一个值,那么合并的observable应该立即发出当前可观察的先前发出的所有值,并与历史的observable断开连接。

Historic: - A - B - C - D - E - F - G - H - I - J|
Current:  - - - - - - E - F - G - H - I - J|
Merged:   - A - B - C - D - EF-G- H - I - J|

3)解决方案应该能够在历史可观察量之前处理来自当前可观察值的值。

Historic: - - - - - A - B - C - D - E - F - G - H - I - J|
Current:  - - C - D - E - F - G - H - I - J - K - L - M - N|
Merged:   - - - - - A - B - CDEF-G-H- I - J - K - L - M - N|

4)如果已经发出了当前可观察值的值,那么解决方案应该跳过它们直到发出新值。

Historic: - A - B - C - D - E - F - G - H - I - J|
Current:  - - - - - - - - B - C - D - E - F - G - H - I - J|
Merged:   - A - B - C - D - - - - - - E - F - G - H - I - J|

5)对于我的用例,我保证当前的可观测量将成为历史的一部分,但为了完整起见,我会想象解决方案将继续从历史可观察的思维中得出第一个元素将出现在稍后点

Historic: - - - - - E - F - G - H - I - J - ... - Z - A|
Current:  - - A - B - C - D - E - F - G - H - I - J|
Merged:   - - - - - E - F - G - H - I - J - ... - Z - ABCDEFGHIJ|

6)我还保证历史可观察者在他们同步后不会与当前的观察者有所不同,但如果由于某种原因他们做了合并的观察者应该已经与它并且不会发现任何差异

Historic: - A - B - C - D - E - D - C - B - A|
Current:  - - - - - - E - F - G - H - I - J|
Merged:   - A - B - C - D - EF-G- H - I - J|

创建工作解决方案的帮助,这里有一些输入数据:

var historic = new Subject<int>();
var current = new Subject<int>();

// query & subscription goes here

historic.OnNext(1);
historic.OnNext(2);
current.OnNext(5);
historic.OnNext(3);
current.OnNext(6);
historic.OnNext(4);
current.OnNext(7);
historic.OnNext(5);
current.OnNext(8);
historic.OnNext(6);
current.OnNext(9);
historic.OnNext(7);
current.OnNext(10);

正确的解决方案应该产生从1到10的数字。

3 个答案:

答案 0 :(得分:1)

假设您想要不同的外观顺序,您可能需要不同的结果,也许这种方法也适用:

       var replayCurrent = current.Replay();
        replayCurrent.Connect();


        var merged = historic
            .Scan(
                new { history = new List<string>(), firstVal = (string)null },
                (state, val) =>
                { state.history.Add(val); return state; }
                )
            .Merge(
                current.Take(1).Select(v => new { history = (List<string>)null, firstVal = v })

                )
            .Scan(new { history = (List<string>)null, firstVal = (string)null },
                (state, val) =>
                new { history = val.history ?? state.history, firstVal = val.firstVal ?? state.firstVal })
            .TakeWhile(v => 
                (null==v.firstVal || ( null!=v.firstVal && !v.history.Contains(v.firstVal)))
                )
            .Select(v=>v.history.Last())
            .Concat(replayCurrent)
            .Distinct();

        merged.Subscribe(x => Console.WriteLine(x));

答案 1 :(得分:0)

回到过去,我终于在我已经进入的方向上取得了一些进展。让它通过描述中的所有测试用例,所以我认为这对我的用例来说已经足够了。建设性反馈总是受到赞赏。

public static IObservable<T> CombineObservables<T>(
    IObservable<T> historicObservable,
    IObservable<T> currentObservable)
    where T : IEquatable<T>
{
    var cachedCurrent = currentObservable.Replay();
    cachedCurrent.Connect();

    var firstMessage = cachedCurrent.FirstAsync();

    var emittedHistoryItems = new List<T>();

    var part1 = historicObservable.TakeUntil(firstMessage)
                                  .Do(x => emittedHistoryItems.Add(x));

    var part2 = historicObservable.CombineLatest(firstMessage, Tuple.Create)
                                  .TakeWhile(x =>
                                             {
                                                 var historyItem = x.Item1;
                                                 var first = x.Item2;

                                                 return !emittedHistoryItems.Any(y => y.Equals(first)) && !historyItem.Equals(first);
                                             })
                                  .Select(x => x.Item1)
                                  .Do(x => emittedHistoryItems.Add(x));

    var part3 = cachedCurrent.SkipWhile(x => emittedHistoryItems.Contains(x));

    return part1.Concat(part2).Concat(part3);
}

小提琴示例:https://dotnetfiddle.net/6BqfiW

答案 2 :(得分:-1)

如果我理解你的问题,你可以

  • 制作一个发出两个可观察者的观察
  • 第一个是'历史',可以注入StartsWith
  • 第二个是'current'并且仅在当前的第一个元素上提供,这意味着您需要使用Take(1)或其他东西来订阅该observable来获取第一个元素,并且。选择可观察的。 (您可以根据需要添加.Where和.Scan来比较2个序列中的值)
  • 将两者与.Switch结合使用。 Switch将取消取消历史记录,并从此开始关注当前的电流。

UPDATE:

我不认为示例数据反映了问题,至少不是我理解的。试试这个

var historic = new Subject<int>();
var current = new Subject<int>();
var observables = new Subject<IObservable<int>>();
int[] tracker = new int[] { int.MaxValue, int.MaxValue };

var merged = historic
                .Select(x => new { source = (int)0, val = x })
                .Merge(current.Select(y => new { source = (int)1, val = y }))
                .Scan((state, v) => {
                        tracker[v.source] = v.val;
                        return v; })
                .Do(x => {
                    if ((tracker[1] <= tracker[0]) && tracker[0] != int.MaxValue)
                        observables.OnNext(current.StartWith(x.val));
                    })
                .Where(x => x.source == 0)
                .Select(x => x.val);

var streamSelector = observables.Switch();

streamSelector.Subscribe(x => Console.WriteLine(x));
observables.OnNext(merged);

historic.OnNext(1);
historic.OnNext(2);
current.OnNext(5);
historic.OnNext(3);
current.OnNext(6);
historic.OnNext(4);
current.OnNext(7);
historic.OnNext(5);
current.OnNext(8);
historic.OnNext(6);
current.OnNext(9);
historic.OnNext(7);
current.OnNext(10);