使用一个observable作为时钟来测试另一个是否超时

时间:2013-11-07 07:11:35

标签: c# system.reactive

我想做的宣言如下:

// Checks input source for timeouts, based on the number of elements received 
// from clock since the last one received from source. 
// The two selectors are used to generate output elements.
public static IObservable<R> TimeoutDetector<T1,T2,R>(
        this IObservable<T1> source, 
        IObservable<T2> clock, 
        int countForTimeout,
        Func<R> timedOutSelector, 
        Func<T1, R> okSelector)

ascii中的大理石图很难,但这里有:

source --o---o--o-o----o-------------------o---
clock  ----x---x---x---x---x---x---x---x---x---
output --^---^--^-^----^-----------!-------^---

我尝试过寻找可以使用Observablesource的现有clock函数,但大多数组合函数都依赖于每个'(AndZip),或者他们从'缺失的'(CombineLatest)重新返回'之前'的值,或者它们离我的距离太远需要(AmbGroupJoinJoinMergeSelectManyTimeout)。 Sample看起来很接近,但我不想将源吞吐量限制为时钟速率。

所以现在我不得不试图填补这里的巨大差距:

return new AnonymousObservable<R>(observer =>
{
    //One observer, two observables??
});

对不起,'你试过的'部分在这里有点弱:让我说我已经尝试过了!我不是要求全面实施,只是:

  • 是否有内置功能可以帮助我,我错过了?
  • 如何构建一个基于lambda的观察者,该观察者订阅了两个可观察者?

4 个答案:

答案 0 :(得分:6)

我知道你并没有要求全面实施,但我认为这是一个解决方案:

public static IObservable<TR> TimeoutDetector<T1, T2, TR>(
    this IObservable<T1> source,
    IObservable<T2> clock,
    int countForTimeout,
    Func<TR> timedOutSelector,
    Func<T1, TR> okSelector)
{
    return source
        .Select(i => clock.Take(countForTimeout).LastAsync())
        .Switch().Select(_ => timedOutSelector())
        .Merge(source.Select(okSelector));
}

它的工作原理如下 - 我注意到你的输出是okSelector投射的源,与超时事件合并。所以我专注于产生超时事件,因为其余的很容易。

这个想法是每次发出源时创建倒计时,并在每个时钟脉冲上递减倒计时。如果源发出,我们中止倒计时,否则当倒计时达到0时,我们会产生timedOut事件。

打破它:

  1. 将每个源元素投影到一个采用第一个countForTimeout元素的流中 - 请注意,时钟流必须是“热”可观察对象,因为我们在每个countDown事件上订阅它。时钟流很热是很正常的。如果这件事发生了,我们就会超时。
  2. Switch将丢弃除最新的倒计时流以外的所有内容。
  3. 使用Select投放到timedOut事件。
  4. 现在只需合并源事件。
  5. 这是我使用的单元测试,旨在与您的大理石图非常相似(nuget rx-testing&amp; nunit用于编译必要的库):

        [Test]
        public void AKindOfTimeoutTest()
        {
            var scheduler = new TestScheduler();
    
            var clockStream = scheduler.CreateHotObservable(
                OnNext(100, Unit.Default),
                OnNext(200, Unit.Default),
                OnNext(300, Unit.Default),
                OnNext(400, Unit.Default),
                OnNext(500, Unit.Default),
                OnNext(600, Unit.Default),
                OnNext(750, Unit.Default), /* make clock funky! */
                OnNext(800, Unit.Default),
                OnNext(900, Unit.Default));
    
    
            var sourceStream = scheduler.CreateColdObservable(
                OnNext(50, 1),
                OnNext(150, 2),
                OnNext(250, 3),
                OnNext(275, 4),
                OnNext(400, 5),
                OnNext(900, 6));
    
    
            Func<int> timedOutSelector = () => 0;
            Func<int, int> okSelector = i => i;
    
            var results = scheduler.CreateObserver<int>();
    
            sourceStream.TimeoutDetector(clockStream, 3, timedOutSelector, okSelector)
                        .Subscribe(results);
    
            scheduler.Start();
    
            results.Messages.AssertEqual(
                OnNext(50, 1),
                OnNext(150, 2),
                OnNext(250, 3),
                OnNext(275, 4),
                OnNext(400, 5),
                OnNext(750, 0),
                OnNext(900, 6));
        }
    }
    

    尝试回答您的具体问题:

    • Q值。是否有内置功能可以帮助我,我错过了?答:可能扫描是关键。
    • Q值。我如何构建一个基于lambda的观察者,该观察者订阅了两个可观察者? A.不太清楚你的意思是什么...有很多方法可以组合流,你提到了大部分。

答案 1 :(得分:2)

这是我提到的Observable.Create方法(相同的测试工作):

public static IObservable<TR> TimeoutDetector<T1, T2, TR>(
    this IObservable<T1> source,
    IObservable<T2> clock,
    int countForTimeout,
    Func<TR> timedOutSelector,
    Func<T1, TR> okSelector)
{
    return Observable.Create<TR>(observer =>
        {
            var counter = countForTimeout;

            var timeoutSub = clock.Subscribe(_ =>
                {
                    var count = Interlocked.Decrement(ref counter);
                    if (count == 0)
                    {
                        observer.OnNext(timedOutSelector());
                    }
                },
                observer.OnError,
                observer.OnCompleted);

            var sourceSub = source.Subscribe(
                i =>
                {
                    Interlocked.Exchange(ref counter, countForTimeout);
                    observer.OnNext(okSelector(i));
                },
                observer.OnError,
                observer.OnCompleted);

            return new CompositeDisposable(sourceSub, timeoutSub);
        });
}

请注意,Observable.Create对于确保使用正确的Rx语法非常有帮助(即流发出OnNext *(OnError | OnCompleted)? - 这意味着我可以稍微放松一下发送OnError或OnCompleted一次。

答案 2 :(得分:0)

我想出了这个,这不如詹姆斯的回答那么漂亮。

public static IObservable<R> TimeoutDetector2<T1, T2, R>(
        this IObservable<T1> source, 
        IObservable<T2> clock, int maxDiff,
        Func<R> timedOutSelector, Func<T1, R> okSelector)
{
    return new AnonymousObservable<R>(observer =>
    {
        int counter = 0;
        object gate = new object();
        bool error = false;
        bool completed = false;
        bool timedOut = false;
        var sourceSubscription = source.Subscribe(
            x =>
            {
                lock(gate)
                {
                    if(!error && !completed) observer.OnNext(okSelector(x));
                    counter = 0;
                    timedOut = false;
                }
            },
            ex =>
            {
                lock(gate)
                {
                    error = true;
                    if(!completed) observer.OnError(ex);
                }
            },
            () =>
            {
                lock(gate)
                {
                    completed = true;
                    if(!error) observer.OnCompleted();
                }
            });
        var clockSubscription = clock.Subscribe(
            x =>
            {
                lock(gate)
                {
                    counter = counter + 1;
                    if(!error && !completed && counter > maxDiff && !timedOut)
                    {
                        timedOut = true;
                        observer.OnNext(timedOutSelector());
                    }
                }
            },
            ex =>
            {
                lock(gate)
                {
                    error = true;
                    if(!completed) observer.OnError(ex);
                }
            },
            () =>
            {
                lock(gate)
                {
                    completed = true;
                    if(!error) observer.OnCompleted();
                }
            });

        //need to return a subscription
        return new CompositeDisposable(sourceSubscription, clockSubscription);
    }).Publish().RefCount(); // prevent subscribers provoking more than one subscription to source and clock
}

答案 3 :(得分:0)

这当然是一个老问题。我一直在寻找比race(throwError('timedout').pipe(delay(10000)), yourObs$) 更高级的功能,以便可以取消超时逻辑。

我想知道超时是否等同于:

react-native start

当然,这里显示的'throwError'可以取消。

如果您想知道为什么-我有一些“步骤”受可观察的链条控制,并且我有超时。但是,如果其中一个步骤包括打开对话框,那么我想取消超时!