简体   繁体   English

如何在 Rx.Net 中实现排放映射处理程序?

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM