C# Rx.NET GroupBy在组终止之前,等待线程完成
我有无限的物体流。我的要求是,来自具有相同密钥的可观察流的每个项目都应该同步处理,而具有不同密钥的所有其他项目可能/应该并行处理。最简单的方法(如大多数地方所述)是使用C# Rx.NET GroupBy在组终止之前,等待线程完成,c#,multithreading,system.reactive,C#,Multithreading,System.reactive,我有无限的物体流。我的要求是,来自具有相同密钥的可观察流的每个项目都应该同步处理,而具有不同密钥的所有其他项目可能/应该并行处理。最简单的方法(如大多数地方所述)是使用GroupByUntil操作符: var results = observableStream .GroupByUntil(item => item.Id, group => group.Throttle(TimeSpan.FromSeconds(30), scheduler)) .Se
GroupByUntil
操作符:
var results = observableStream
.GroupByUntil(item => item.Id, group =>
group.Throttle(TimeSpan.FromSeconds(30), scheduler))
.SelectMany(group =>
group
.ObserveOn(scheduler)
.Select(item => ProcessItem(item)));
var disposable = results.Subscribe(result => SaveResults(result));
在我能够保证执行ProcessItem(item)
所需时间不超过30秒之前,代码运行良好。否则group.Throttle(TimeSpan.FromSeconds(30),调度器)
将关闭组的流,并且新项目到达并在新线程上开始处理的概率非常高
因此,基本上我需要知道我的线程已经完成了对所有具有特定键的项目的处理,并且我需要在GroupByUntil的durationSelector
中通知操作员参数
关于如何实现这一点有什么想法吗?提前谢谢。这与这个问题非常相似:
从这个问题的答案来看,有一个操作员排水管:
public static class ObservableDrainExtensions
{
public static IObservable<TOut> Drain<TSource, TOut>(this IObservable<TSource> source,
Func<TSource, IObservable<TOut>> selector)
{
return Observable.Defer(() =>
{
BehaviorSubject<Unit> queue = new BehaviorSubject<Unit>(new Unit());
return source
.Zip(queue, (v, q) => v)
.SelectMany(v => selector(v)
.Do(_ => { }, () => queue.OnNext(new Unit()))
);
});
}
}
给定一个看起来像A1、A2、B1、A3、B2、C1、B3、C2的流,GroupBy
通过ID分离流:
A: A1, A2, A3
B: B1, B2, B3
C: C1, C2
…和Drain
确保对于给定子流中的项,它们是串行运行的,而不是并行运行的 您似乎需要RxJS操作符的一个变体:
var results = observableStream
.GroupByUntil(item => item.Id, group =>
group.Throttle(TimeSpan.FromSeconds(30), scheduler))
.SelectMany(group =>
group
.ObserveOn(scheduler)
.Select(item => ProcessItem(item)));
var disposable = results.Subscribe(result => SaveResults(result));
将每个源值投影到一个可观测值,仅当前一个投影可观测值已完成时,该值才会合并到输出可观测值中
可以找到该操作员的Rx实现(排气图
)。在您的例子中,您只需要对可观察序列的每个分组子序列应用相同的逻辑:
/// <summary>Projects each element to an observable sequence, which is merged
/// in the output observable sequence only if the previous projected observable
/// sequence that has the same key has completed.</summary>
public static IObservable<TResult> ExhaustMapPerKey<TSource, TKey, TResult>(
this IObservable<TSource> source,
Func<TSource, TKey> keySelector,
Func<TSource, TKey, IObservable<TResult>> function,
IEqualityComparer<TKey> keyComparer = default)
{
keyComparer ??= EqualityComparer<TKey>.Default;
return source
.GroupBy(keySelector, keyComparer)
.SelectMany(group => Observable.Using(() => new SemaphoreSlim(1, 1),
semaphore => group
.SelectMany(item => Observable.If(() => semaphore.Wait(0),
Observable.Defer(() => function(item, group.Key))
.Finally(() => semaphore.Release())))));
}
此解决方案限制每个子序列,但不限制整个操作的并发程度。如果唯一密钥的数量恰好很大,并发性可能会变得相当高。要限制全局并发级别,除了用于限制每个组的并发级别外,还可以使用额外的并发级别:
public static IObservable<TResult> ExhaustMapPerKey<TSource, TKey, TResult>(
this IObservable<TSource> source,
Func<TSource, TKey> keySelector,
Func<TSource, TKey, IObservable<TResult>> function,
int maximumConcurrency,
IEqualityComparer<TKey> keyComparer = default)
{
keyComparer ??= EqualityComparer<TKey>.Default;
return Observable.Using(() => new SemaphoreSlim(maximumConcurrency, maximumConcurrency),
globalSemaphore => source
.GroupBy(keySelector, keyComparer)
.SelectMany(group => Observable.Using(() => new SemaphoreSlim(1, 1),
localSemaphore => group
.SelectMany(item => Observable.If(() => localSemaphore.Wait(0),
Observable.If(() => globalSemaphore.Wait(0),
Observable.Defer(() => function(item, group.Key))
.Finally(() => globalSemaphore.Release()))
.Finally(() => localSemaphore.Release()))))));
}
public static IObservable exhaust mapperkey(
这是一个可观测的来源,
Func键选择器,
Func函数,
int最大并发性,
IEqualityComparer键比较器=默认值)
{
keyComparer???=EqualityComparer.Default;
返回可观察的。使用(()=>新信号量lim(maximumConcurrency,maximumConcurrency),
globalSemaphore=>source
.GroupBy(键选择器、键比较器)
.SelectMany(group=>Observable。使用(()=>newsemaphoreslim(1,1),
localSemaphore=>group
.SelectMany(item=>Observable.If(()=>localSemaphore.Wait(0)),
可观察。如果(()=>全局信号量。等待(0),
可观察的延迟(()=>函数(项、组、键))
.Finally(()=>globalSemaphore.Release())
.最后(()=>localSemaphore.Release());
}
使用此实现,当具有相同密钥的前一个元素的异步处理正在进行,或者无法根据全局信号量策略立即启动处理时,元素将被删除。您如何知道您已收到某个特定密钥的最后一个?@NetMage实际上我不知道。我试图实现的是,只有当处理特定组的线程完成了它的工作并且队列中不再有任何内容时,我才应该开始限制(取消抖动)。ProcessItem是否同步?它是异步的吗?它是否返回IObservable
?@Shlomo它不是异步的,但它将返回observatable。这是一个不错的解决方案,但只使用GroupBy
组不会被破坏,如果有大量的唯一键,我可能会耗尽内存。
public static IObservable<TResult> ExhaustMapPerKey<TSource, TKey, TResult>(
this IObservable<TSource> source,
Func<TSource, TKey> keySelector,
Func<TSource, TKey, IObservable<TResult>> function,
int maximumConcurrency,
IEqualityComparer<TKey> keyComparer = default)
{
keyComparer ??= EqualityComparer<TKey>.Default;
return Observable.Using(() => new SemaphoreSlim(maximumConcurrency, maximumConcurrency),
globalSemaphore => source
.GroupBy(keySelector, keyComparer)
.SelectMany(group => Observable.Using(() => new SemaphoreSlim(1, 1),
localSemaphore => group
.SelectMany(item => Observable.If(() => localSemaphore.Wait(0),
Observable.If(() => globalSemaphore.Wait(0),
Observable.Defer(() => function(item, group.Key))
.Finally(() => globalSemaphore.Release()))
.Finally(() => localSemaphore.Release()))))));
}