Write an Rx “RetryAfter” extension method

前端 未结 6 928
花落未央
花落未央 2020-11-27 16:00

In the book IntroToRx the author suggest to write a \"smart\" retry for I/O which retry an I/O request, like a network request, after a period of time.

Here is the e

6条回答
  •  余生分开走
    2020-11-27 16:37

    Maybe I'm over simplifying the situation, but if we look at the implementation of Retry, it is simply an Observable.Catch over an infinite enumerable of observables:

    private static IEnumerable RepeatInfinite(T value)
    {
        while (true)
            yield return value;
    }
    
    public virtual IObservable Retry(IObservable source)
    {
        return Observable.Catch(QueryLanguage.RepeatInfinite(source));
    }
    

    So if we take this approach, we can just add a delay after the first yield.

    private static IEnumerable> RepeateInfinite (IObservable source, TimeSpan dueTime)
    {
        // Don't delay the first time        
        yield return source;
    
        while (true)
            yield return source.DelaySubscription(dueTime);
        }
    
    public static IObservable RetryAfterDelay(this IObservable source, TimeSpan dueTime)
    {
        return RepeateInfinite(source, dueTime).Catch();
    }
    

    An overload that catches a specific exception with a retry count can be even more concise:

    public static IObservable RetryAfterDelay(this IObservable source, TimeSpan dueTime, int count) where TException : Exception
    {
        return source.Catch(exception =>
        {
            if (count <= 0)
            {
                return Observable.Throw(exception);
            }
    
            return source.DelaySubscription(dueTime).RetryAfterDelay(dueTime, --count);
        });
    }
    

    Note that the overload here is using recursion. On first appearances, it would seem that a StackOverflowException is possible if count was something like Int32.MaxValue. However, DelaySubscription uses a scheduler to run the subscription action, so stack overflow would not be possible (i.e. using "trampolining"). I guess this isn't really obvious by looking at the code though. We could force a stack overflow by explicitly setting the scheduler in the DelaySubscription overload to Scheduler.Immediate, and passing in TimeSpan.Zero and Int32.MaxValue. We could pass in a non-immediate scheduler to express our intent a little more explicitly, e.g.:

    return source.DelaySubscription(dueTime, TaskPoolScheduler.Default).RetryAfterDelay(dueTime, --count);
    

    UPDATE: Added overload to take in a specific scheduler.

    public static IObservable RetryAfterDelay(
        this IObservable source,
        TimeSpan retryDelay,
        int retryCount,
        IScheduler scheduler) where TException : Exception
    {
        return source.Catch(
            ex =>
            {
                if (retryCount <= 0)
                {
                    return Observable.Throw(ex);
                }
    
                return
                    source.DelaySubscription(retryDelay, scheduler)
                        .RetryAfterDelay(retryDelay, --retryCount, scheduler);
            });
    } 
    

提交回复
热议问题