将GroupBy与Concat一起使用仅返回第一个组值

时间:2017-03-03 15:01:50

标签: c# system.reactive

我有一些GroupBy的测试代码可以正常工作......

代码

var sw = Stopwatch.StartNew();
int groupSize = 5;

var coreObservable = Observable
    .Range(1, 20)
    .Select((x, idx) => new { x, idx })
    .GroupBy(x => x.idx / groupSize, x => x.x)
    .Select(x => x.ToList())
    .Replay()
    .RefCount();

coreObservable.Subscribe(
    x => x.Subscribe(y => Console.WriteLine("Event raised [Books: {0}, Timestamp: {1}]", string.Join("|", y), sw.Elapsed)),
    () => Console.WriteLine("Subcription closed"));

coreObservable.Wait(); // blocking until observable completes

输出

Event raised [Values: 1|2|3|4|5, Timestamp: 00:00:00.3224002]
Event raised [Values: 6|7|8|9|10, Timestamp: 00:00:00.3268353]
Event raised [Values: 11|12|13|14|15, Timestamp: 00:00:00.3270101]
Event raised [Values: 16|17|18|19|20, Timestamp: 00:00:00.3270803]
Subcription closed

问题是当我尝试将Concat与此表达式一起使用时......

代码

var sw = Stopwatch.StartNew();
int groupSize = 5;

var coreObservable = Observable
    .Range(1, 20)
    .Select((x, idx) => new { x, idx })
    .GroupBy(x => x.idx / groupSize, x => x.x)
    .Select(x => x.ToList())
    .Concat() // JUST ADDED THIS
    .Replay()
    .RefCount();

coreObservable.Subscribe(
    x => Console.WriteLine("Event raised [Values: {0}, Timestamp: {1}]", string.Join("|", x), sw.Elapsed),
    () => Console.WriteLine("Subcription closed"));

coreObservable.Wait(); // blocking until observable completes

输出

Event raised [Values: 1|2|3|4|5, Timestamp: 00:00:00.2728469]
Event raised [Values: , Timestamp: 00:00:00.2791311]
Event raised [Values: , Timestamp: 00:00:00.2793720]
Event raised [Values: , Timestamp: 00:00:00.2794617]
Subcription closed

注意只暴露了第一组值。

我使用GroupBy而不是Buffer的原因是因为我试图将其用作为突发数据源创建最大大小块的方法。原始的observable可能是项目数组,我想在单个事件中有太多项目时拆分数组。

我想使用Concat的原因是因为我希望能够在数组事件之间创建延迟,就像很多人推荐here一样。

2 个答案:

答案 0 :(得分:2)

Concat()替换为Merge(),它可以正常工作。

我相信你的问题的原因是,Concat()在当前完成之前不会开始收听下一个序列。

Concat图表:

s1 --0--1--2-|
s2           -5--6--7--8--|
r  --0--1--2--5--6--7--8--|

虽然Merge()同时订阅所有子序列,但每当任何子项发布值时都会发布值。

合并图:

s1 --1--1--1--|
s2 ---2---2---2|
r  --12-1-21--2|

因此,在您的情况下,Concat()订阅IObservable<IList<int>>中的第一个Select(x => x.ToList()),发布值直到完成,然后订阅下一个序列。 GroupBy()会为找到的每个组创建一个新的IGroupedObservable流,但所有IGroupedObservable将同时完成:当基础流完成时。

所以Concat()监听第一个流直到它完成,但是当第一个流完成时,所有其他流也完成了(因为它们实际上都是相同的序列,只是按键分割),所以有没有为以下序列发布的值。

所有图表都是从here借来的,这是Rx的绝佳资源,我强烈建议您查看有关各种运算符如何工作的任何问题。

答案 1 :(得分:0)

你的问题可以减少到这样的事情,这可能更容易思考:

var sw = Stopwatch.StartNew();
var subject = new Subject<int>();

var o2 = subject.Where(i => i % 2 == 0).ToList();
var o3 = subject.Where(i => i % 3 == 0).ToList();
var o4 = subject.Where(i => i % 4 == 0).ToList();

var c = Observable.Concat(o2, o3, o4)
//      .Replay()
//      .RefCount() 
//.Replay().RefCount() has no impact here.
    ;

c.Subscribe(
    x => Console.WriteLine("Event raised [Values: {0}, Timestamp: {1}]", string.Join("|", x), sw.Elapsed),
    () => Console.WriteLine("Subcription closed"));

for(int i = 0; i < 6; i++)
    subject.OnNext(i);
subject.OnCompleted();

输出:

Event raised [Values: 0|2|4, Timestamp: 00:00:00.0002278]
Event raised [Values: , Timestamp: 00:00:00.0002850]
Event raised [Values: , Timestamp: 00:00:00.0003049]
Subcription closed

如果您使用大理石图表,它会是这样的:

s   : 012345|
o2  : ------(024)|
o3  : ------(03) |
o4  : ------(04) |

cOut: ------(024)|
cSub: (So2)------(So3)(So4) 

cSub shows when c subscribes to child observables. cOut shows c's output. 
So2 means subscribe to o2, So3 means subscribe to o3, etc..

Concat订阅传递给它的第一个observable,然后只在当前的observable完成时才订阅后续的observable。在我们的例子中,ToList在源完成时转储整个列表时不会省略任何内容。因此o2o3o4同时完成,但c仅订阅o2。在o2完成后,它会尝试订阅其他人,但他们已经完成了。

至于如何修复它,Merge会起作用,但我猜你想在第2组之前处理第1组,Merge会破坏。