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
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);
});
}