Я искал реализацию базовой логики повтора для простого сетевого потока. Идея состоит в том, что есть три типа исключений, которые нужно обрабатывать:
Набрасывая это, я получаю что-то вроде
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));
Обновлено: этот пример сильно упрощен и показывает только три типичных шаблона обработки ошибок, которые я вижу в своем коде. В реальном мире, конечно, это намного больше.
Код работает, но выглядит слишком сложным, и я не уверен, ударит ли меня особое значение null в какой-то момент.
Я также пробовал комбинации .Where() и .Merge(), но код быстро становится нечитаемым.
Есть ли более чистый способ (т.е. более канонический) для выполнения этой базовой обработки ошибок?





Для этого уже есть отличные библиотеки, например Полли. Но, если вы хотите реализовать свое собственное, вы можете сделать что-то вроде следующего.
Вы можете создать класс RetryPolicyHandler. Кроме того, вы можете абстрагировать его с помощью интерфейса и использовать с любым из контейнеров IoC, если хотите. Добавьте такой метод, как RetryExecuteAsync, со следующей реализацией.
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;
}
}
}
И тогда вы можете использовать это вот так.
await this.retryPolicyHandler.RetryExecuteAsync(async () =>
{
// Whatever code you want to retry
}).ConfigureAwait(false);
Вы также можете создать другие специальные методы, если хотите. Например, ExecuteDeadlockRetryAsync с улавливанием SqlException или ExecuteHttpCallRetryAsync для обработки HttpRequestException и обработки их другим способом.
Это немного помогает:
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));
Которая использует следующие сообщения расширения:
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)));
}
}
Спасибо за ссылку на Полли, выглядит полезно! Но даже имея библиотеку, нужно было бы реализовать это в Rx. Меня больше интересовало, как это сделать конкретно в Rx, а не хорошая реализация для этого.