我有一个IObservable<IObservable<T>>
,其中每个内部IObservable<T>
是一个值流,后跟最终的OnCompleted
事件。
我想将其转换为IObservable<IEnumerable<T>>
,这是一个由未完成的任何内部流的最新值组成的流。每当从其中一个内部流(或内部流过期)生成新值时,它应该生成一个新的IEnumerable<T>
最容易用大理石图表显示(我希望它足够全面):
input ---.----.---.----------------
| | '-f-----g-|
| 'd------e---------|
'a--b----c-----|
result ---a--b-b--c-c-c-e-e-e---[]-
d d d e f g
f f
([]
为空IEnumerable<T>
,-|
代表OnCompleted)
您可以看到它略微类似于CombineLatest
操作。
我一直在玩Join
和GroupJoin
但无济于事,但我觉得这几乎肯定是前进的正确方向。
我想在此运算符中使用尽可能少的状态。
我已更新此问题,不仅包括单值序列 - 结果IObservable<IEnumerable<T>>
应仅包含每个序列的最新值 - 如果序列未生成值,则不应包含该值。< / p>
答案 0 :(得分:3)
这是昨天基于您的解决方案的版本,针对新要求进行了调整。基本思路是将引用放入易腐烂的集合中,然后在内部序列产生新值时更新引用的值。
我还修改了以正确跟踪内部订阅并取消订阅外部observable是否取消订阅。
如果任何流产生错误,也会进行修改以将其全部拆除。
最后,我修正了一些可能违反Rx准则的竞争条件。如果你的内部observable是从不同的线程同时触发,你可以同时结束调用obs.OnNext
,这是一个很大的禁忌。所以我使用相同的锁来控制每个内部observable以防止这种情况(参见Synchronize
调用)。请注意,因为这样,您可能会使用常规的双链表而不是PerishableCollection
,因为现在使用该集合的所有代码都在一个锁中,因此您不需要线程保证PerishableCollection
。
// Acts as a reference to the current value stored in the list
private class BoxedValue<T>
{
public T Value;
public BoxedValue(T initialValue) { Value = initialValue; }
}
public static IObservable<IEnumerable<T>> MergeLatest<T>(this IObservable<IObservable<T>> source)
{
return Observable.Create<IEnumerable<T>>(obs =>
{
var collection = new PerishableCollection<BoxedValue<T>>();
var outerSubscription = new SingleAssignmentDisposable();
var subscriptions = new CompositeDisposable(outerSubscription);
var innerLock = new object();
outerSubscription.Disposable = source.Subscribe(duration =>
{
BoxedValue<T> value = null;
var lifetime = new DisposableLifetime(); // essentially a CancellationToken
var subscription = new SingleAssignmentDisposable();
subscriptions.Add(subscription);
subscription.Disposable = duration.Synchronize(innerLock)
.Subscribe(
x =>
{
if (value == null)
{
value = new BoxedValue<T>(x);
collection.Add(value, lifetime.Lifetime);
}
else
{
value.Value = x;
}
obs.OnNext(collection.CurrentItems().Select(p => p.Value.Value));
},
obs.OnError, // handle an error in the stream.
() => // on complete
{
if (value != null)
{
lifetime.Dispose(); // removes the item
obs.OnNext(collection.CurrentItems().Select(p => p.Value.Value));
subscriptions.Remove(subscription); // remove this subscription
}
}
);
});
return subscriptions;
});
}
答案 1 :(得分:0)
此解决方案适用于单项流,但不幸的是,它会累积内部流中的每个项目,直到完成。
public static IObservable<IEnumerable<T>> MergeLatest<T>(this IObservable<IObservable<T>> source)
{
return Observable.Create<IEnumerable<T>>(obs =>
{
var collection = new PerishableCollection<T>();
return source.Subscribe(duration =>
{
var lifetime = new DisposableLifetime(); // essentially a CancellationToken
duration
.Subscribe(
x => // on initial item
{
collection.Add(x, lifetime.Lifetime);
obs.OnNext(collection.CurrentItems().Select(p => p.Value));
},
() => // on complete
{
lifetime.Dispose(); // removes the item
obs.OnNext(collection.CurrentItems().Select(p => p.Value));
}
);
});
});
}
答案 2 :(得分:0)
public static IObservable<IEnumerable<T>> CombineLatestEagerly<T>(this IObservable<IObservable<T>> source)
{
return source
// Reify completion to force an additional combination:
.Select(o => o.Select(v => new { Value = v, HasValue = true })
.Concat(Observable.Return(new { Value = default(T), HasValue = false })))
// Merge a completed observable to force combination with the first real inner observable:
.Merge(Observable.Return(Observable.Return(new { Value = default(T), HasValue = false })))
.CombineLatest()
// Filter out completion notifications:
.Select(l => l.Where(v => v.HasValue).Select(v => v.Value));
}