[英]How can I implement an exhaustMap handler in Rx.Net?
I am looking for something similar to the exhaustMap
operator from rxjs
, but RX.NET
does not seem to have such an operator.我正在寻找类似于
exhaustMap
运算符的rxjs
,但RX.NET
似乎没有这样的运算符。
What I need to achieve is that, upon every element of the source stream, I need to start an async
handler, and until it finishes, I would like to drop any elements from the source.我需要实现的是,在源流的每个元素上,我需要启动一个
async
处理程序,并且在它完成之前,我想从源中删除任何元素。 As soon as the handler finishes, resume taking elements.处理程序完成后,立即恢复获取元素。
What I don't want is to start an async handler upon every element - while the handler runs, I want to drop source elements.我不想在每个元素上启动一个异步处理程序——当处理程序运行时,我想删除源元素。
I also suspect I need to cleverly use the defer operator here?我还怀疑我需要在这里巧妙地使用 defer 运算符?
Thank you!谢谢!
Here is an implementation of theExhaustMap
operator.这是
ExhaustMap
运算符的实现。 The source observable is projected to an IObservable<Task<TResult>>
, where each subsequent task is either the previous one if it's still running, or otherwise a new task associated with the current item.源 observable 被投影到
IObservable<Task<TResult>>
,其中每个后续任务要么是前一个任务(如果它仍在运行),要么是与当前项目相关联的新任务。 Repeated occurrences of the same task are then removed with the DistinctUntilChanged
operator, and finally the observable is flattened with the Concat
operator.然后使用
DistinctUntilChanged
运算符删除重复出现的相同任务,最后使用Concat
运算符将 observable 展平。
/// <summary>Invokes an asynchronous function for each element of an observable
/// sequence, ignoring elements that are emitted before the completion of an
/// asynchronous function of a preceding element.</summary>
public static IObservable<TResult> ExhaustMap<TSource, TResult>(
this IObservable<TSource> source,
Func<TSource, Task<TResult>> function)
{
return source
.Scan(Task.FromResult<TResult>(default), (previousTask, item) =>
{
return !previousTask.IsCompleted ? previousTask : HideIdentity(function(item));
})
.DistinctUntilChanged()
.Concat();
async Task<TResult> HideIdentity(Task<TResult> task) => await task;
}
The tasks returned by the function
are not guaranteed to be distinct, hence the need for the HideIdentity
local function that returns distinct wrappers of the tasks. function
返回的任务不能保证是不同的,因此需要HideIdentity
本地函数来返回任务的不同包装器。
Usage example:用法示例:
Observable
.Interval(TimeSpan.FromMilliseconds(200))
.Select(x => (int)x + 1)
.Take(10)
.Do(x => Console.WriteLine($"Input: {x}"))
.ExhaustMap(async x => { await Task.Delay(x % 3 == 0 ? 500 : 100); return x; })
.Do(x => Console.WriteLine($"Result: {x}"))
.Wait();
Output:输出:
Input: 1
Result: 1
Input: 2
Result: 2
Input: 3
Input: 4
Input: 5
Result: 3
Input: 6
Input: 7
Input: 8
Result: 6
Input: 9
Input: 10
Result: 9
Update: Here is an alternative implementation, where the function
produces an IObservable<TResult>
instead of a Task<TResult>
:更新:这是一个替代实现,其中
function
生成IObservable<TResult>
而不是Task<TResult>
:
/// <summary>Projects each element to an observable sequence, which is merged
/// in the output observable sequence only if the previous projected observable
/// sequence has completed.</summary>
public static IObservable<TResult> ExhaustMap<TSource, TResult>(
this IObservable<TSource> source,
Func<TSource, IObservable<TResult>> function)
{
return Observable.Using(() => new SemaphoreSlim(1, 1),
semaphore => source.SelectMany(item => ProjectItem(item, semaphore)));
IObservable<TResult> ProjectItem(TSource item, SemaphoreSlim semaphore)
{
// Attempt to acquire the semaphore immediately. If successful, return
// a sequence that releases the semaphore when terminated. Otherwise,
// return immediately an empty sequence.
return Observable.If(() => semaphore.Wait(0),
Observable
.Defer(() => function(item))
.Finally(() => semaphore.Release())
);
}
}
This one is interesting because it makes it very easy to modify the degree of parallelism, if needed.这个很有趣,因为如果需要,它可以很容易地修改并行度。 Just instantiate the
SemaphoreSlim
with an initialCount
argument different than 1.只需使用不同于 1 的
initialCount
参数实例化SemaphoreSlim
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.