简体   繁体   English

如何在 rx.net 中实现我自己的操作符

[英]How to implement my own operator in rx.net

I need the functionality of a hysteresis filter in RX.我需要 RX 中的磁滞滤波器的功能。 It should emit a value from the source stream only when the previously emitted value and the current input value differ by a certain amount.仅当先前发出的值与当前输入值相差一定量时,它才应从源 stream 发出一个值。 As a generic extension method, it could have the following signature:作为通用扩展方法,它可以具有以下签名:

public static IObservable<T> HysteresisFilter<T>(this IObservable<t> source, Func<T/*previously emitted*/, T/*current*/, bool> filter)

I was not able to figure out how to implement this with existing operators.我无法弄清楚如何使用现有的运营商来实现这一点。 I was looking for something like lift from RxJava, any other method to create my own operator.我一直在寻找像 RxJava 中的lift之类的东西,任何其他方法来创建我自己的运算符。 I have seen this checklist , but I haven't found any example on the web.我看过这个清单,但我没有在 web 上找到任何示例。

The following approaches (both are actually the same) which seem workaround to me work, but is there a more Rx way to do this, like without wrapping a subject or actually implementing an operator?以下方法(实际上都是相同的)对我来说似乎可以解决问题,但是是否有更多的Rx 方法可以做到这一点,比如不包装subject或实际实现运算符?

async Task Main()
{
    var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));

    var rnd = new Random();
    var s = Observable.Interval(TimeSpan.FromMilliseconds(10))
            .Scan(0d, (a,_) => a + rnd.NextDouble() - 0.5)
            .Publish()
            .AutoConnect()
            ;

    s.Subscribe(Console.WriteLine, cts.Token);

    s.HysteresisFilter((p, c) => Math.Abs(p - c) > 1d).Subscribe(x => Console.WriteLine($"1> {x}"), cts.Token);
    s.HysteresisFilter2((p, c) => Math.Abs(p - c) > 1d).Subscribe(x => Console.WriteLine($"2> {x}"), cts.Token);

    await Task.Delay(Timeout.InfiniteTimeSpan, cts.Token).ContinueWith(_=>_, TaskContinuationOptions.OnlyOnCanceled);
}

public static class ReactiveOperators
{
    public static IObservable<T> HysteresisFilter<T>(this IObservable<T> source, Func<T, T, bool> filter)
    {
        return new InternalHysteresisFilter<T>(source, filter).AsObservable; 
    }

    public static IObservable<T> HysteresisFilter2<T>(this IObservable<T> source, Func<T, T, bool> filter)
    {
        var subject = new Subject<T>();
        T lastEmitted = default;
        bool emitted = false;

        source.Subscribe(
            value =>
            {
                if (!emitted || filter(lastEmitted, value))
                {
                    subject.OnNext(value);
                    lastEmitted = value;
                    emitted = true;
                }
            } 
            , ex => subject.OnError(ex)
            , () => subject.OnCompleted()
        );

        return subject;
    }

    private class InternalHysteresisFilter<T>: IObserver<T>
    {
        Func<T, T, bool> filter;
        T lastEmitted;
        bool emitted;

        private readonly Subject<T> subject = new Subject<T>();

        public IObservable<T> AsObservable => subject;

        public InternalHysteresisFilter(IObservable<T> source, Func<T, T, bool> filter)
        {
            this.filter = filter;
            source.Subscribe(this);
        }

        public IDisposable Subscribe(IObserver<T> observer)
        {
            return subject.Subscribe(observer);
        }

        public void OnNext(T value)
        {
            if (!emitted || filter(lastEmitted, value))
            {
                subject.OnNext(value);
                lastEmitted = value;
                emitted = true;
            }
        }

        public void OnError(Exception error)
        {
            subject.OnError(error);
        }

        public void OnCompleted()
        {
            subject.OnCompleted();
        }
    }
}

Sidenote: There will be several thousand of such filters applied to as many streams.旁注:将有数千个这样的过滤器应用于尽可能多的流。 I need throughput over latency, thus I am looking for the solution with the minimum of overhead both in CPU and in memory even if others look fancier.我需要吞吐量超过延迟,因此我正在寻找 CPU 和 memory 开销最小的解决方案,即使其他人看起来更漂亮。

Most examples I've seen in the book Introduction to Rx are using the method Observable.Create for creating new operators.我在《 Rx 简介》一书中看到的大多数示例都使用Observable.Create方法来创建新的运算符。

The Create factory method is the preferred way to implement custom observable sequences. Create factory 方法是实现自定义可观察序列的首选方法。 The usage of subjects should largely remain in the realms of samples and testing.主题的使用应主要停留在样本和测试领域。 ( citation ) 引用

public static IObservable<T> HysteresisFilter<T>(this IObservable<T> source,
    Func<T, T, bool> predicate)
{
    return Observable.Create<T>(observer =>
    {
        T lastEmitted = default;
        bool emitted = false;
        return source.Subscribe(value =>
        {
            if (!emitted || predicate(lastEmitted, value))
            {
                observer.OnNext(value);
                lastEmitted = value;
                emitted = true;
            }
        }, observer.OnError, observer.OnCompleted);
    });
}

This answer is the same is equivalent to @Theodor's, but it avoids using Observable.Create , which I generally would avoid.这个答案与@Theodor 的答案相同,但它避免使用我通常会避免的Observable.Create

public static IObservable<T> HysteresisFilter2<T>(this IObservable<T> source,
    Func<T, T, bool> predicate)
{
    return source
        .Scan((emitted: default(T), isFirstItem: true, emit: false), (state, newItem) => state.isFirstItem || predicate(state.emitted, newItem)
            ? (newItem, false, true)
            : (state.emitted, false, false)
        )
        .Where(t => t.emit)
        .Select(t => t.emitted);
}

.Scan is what you want to use when you're tracking state across items within an observable. .Scan是您在跨可观察对象中的项目跟踪 state 时要使用的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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