Reputation: 32497
I have been looking at implementing a basic retry logic for a simple network stream. The idea is that there are three types of exceptions to handle:
While sketching this up, I end up with something like
var retriedObservable = observable.RetryWhen(x => x.SelectMany(ex =>
{
if (ex is ShouldCompleteException)
{
return Observable.Return<Exception>(null);
}
else if (ex is TransientNetworkException)
{
return Observable.Return(ex).Delay(TimeSpan.FromSeconds(1));
}
else
{
return Observable.Throw<Exception>(ex);
}
}).TakeUntil(ex => ex == null));
Edit: This example is highly simplified, showing only the three typical error handling patterns I see in my code. In the real world, there's a lot more to it of course.
The code works but looks overly complicated, and I'm not sure if the special 'null' value will hit me in the face at some point.
I also tried combinations of .Where()
and .Merge()
, but the code quickly becomes unreadable.
Is there a cleaner way (i.e. more canonical) to accomplish this basic error handling?
Upvotes: 0
Views: 837
Reputation: 14350
This helps a little:
var retriedObservable2 = observable.RetryWhen(exStream => Observable.Merge(
exStream.OfType<ShouldCompleteException>().Select(_ => (Exception)null),
exStream.NotOfType(typeof(ShouldCompleteException)).OfType<TransientNetworkException>().Delay(TimeSpan.FromSeconds(1)),
exStream.NotOfTypes(typeof(ShouldCompleteException), typeof(TransientNetworkException)).SelectMany(e => Observable.Throw<Exception>(e))
).TakeUntil(ex => ex == null));
Which uses the following extension messages:
public static class X
{
public static IObservable<TSource> NotOfType<TSource>(this IObservable<TSource> source, Type t)
{
return source.Where(o => !t.IsInstanceOfType(o));
}
public static IObservable<TSource> NotOfTypes<TSource>(this IObservable<TSource> source, params Type[] ts)
{
return source.Where(o => ts.All(t => !t.IsInstanceOfType(o)));
}
}
Upvotes: 2
Reputation: 1185
There are already great libraries for this, like Polly. But, if you want to implement your own, you can do something like the following.
You can create a RetryPolicyHandler
class. Also, you can abstract it with an interface and use with any of the IoC containers if you want. Add a method like RetryExecuteAsync
with the following implementation.
public async Task RetryExecuteAsync(Func<Task> action)
{
int retryCount = default(int);
int maxNumberOfRetries = 5; // Or get from settings
while (true)
{
try
{
await action().ConfigureAwait(false);
break;
}
catch (ArgumentNullException)
{
// Something specific about this exception
if (++retryCount > maxNumberOfRetries)
throw;
}
catch (Exception)
{
if (++retryCount > maxNumberOfRetries)
throw;
}
}
}
And then you can use it like this.
await this.retryPolicyHandler.RetryExecuteAsync(async () =>
{
// Whatever code you want to retry
}).ConfigureAwait(false);
You can also create other specific methods if you want. For instance, ExecuteDeadlockRetryAsync
with catching SqlException
or ExecuteHttpCallRetryAsync
for handling HttpRequestException
and handle them in another way.
Upvotes: 0