写一个Rx和QUOT; RetryAfter"扩展方法 [英] Write an Rx "RetryAfter" extension method

查看:177
本文介绍了写一个Rx和QUOT; RetryAfter"扩展方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在书中 IntroToRx 的笔者建议写一个智能重试I / O其中重试I / O请求,就像一个网络请求,经过一段时间后。

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.

下面是确切的段落:

一个有用的扩展方法添加到您自己的库可能是一个回
  关闭和重试的方法。我与发现工作队这样的
  执行I / O,特别是网络请求时功能非常有用。该
  概念是尝试,并在失败等待给定的时间周期和
  然后再试一次。你的这种方法的版本可能会考虑
  要重试上的异常的类型,以及最大数目
  的重试次数。你甚至可能要延长等待期
  要在每个后续重试较少攻击性。

A useful extension method to add to your own library might be a "Back Off and Retry" method. The teams I have worked with have found such a feature useful when performing I/O, especially network requests. The concept is to try, and on failure wait for a given period of time and then try again. Your version of this method may take into account the type of Exception you want to retry on, as well as the maximum number of times to retry. You may even want to lengthen the to wait period to be less aggressive on each subsequent retry.

不幸的是,我无法弄清楚如何写这个方法。 (

Unfortunately, I can't figure out how to write this method. :(

推荐答案

关键就在这实施后退重试的是的推迟观测的。直到有人赞同其递延观察到的将不会执行出厂。它会调用工厂为每个订阅,因此非常适合我们的重试的场景。

The key to this implementation of a back off retry is deferred observables. A deferred observable won't execute its factory until someone subscribes to it. And it will invoke the factory for each subscription, making it ideal for our retry scenario.

假设我们有触发的网络请求的方法。

Assume we have a method which triggers a network request.

public IObservable<WebResponse> SomeApiMethod() { ... }

有关这个小片段的目的,让我们定义为递延

For the purposes of this little snippet, let's define the deferred as source

var source = Observable.Defer(() => SomeApiMethod());

每当有人订阅源它将调用SomeApiMethod并推出一个新的Web请求。每当它失败,将使用内置的重试运营的幼稚的方式来重试。

Whenever someone subscribes to source it will invoke SomeApiMethod and launch a new web request. The naive way to retry it whenever it fails would be using the built in Retry operator.

source.Retry(4)

这不会是很不错的,虽然API,它不是你要求什么。我们需要延迟请求的发起在每次尝试之间。这样做的一种方式是用<一href=\"https://github.com/mono/rx/blob/master/Rx/NET/Source/System.Reactive.Linq/Reactive/Linq/Observable/DelaySubscription.cs\">delayed订阅。

That wouldn't be very nice to the API though and it's not what you're asking for. We need to delay the launching of requests in between each attempt. One way of doing that is with a delayed subscription.

Observable.Defer(() => source.DelaySubscription(TimeSpan.FromSeconds(1))).Retry(4)

这不是理想的,因为它甚至会在第一次请求增加的延迟,让我们来解决这个问题。

That's not ideal since it'll add the delay even on the first request, let's fix that.

int attempt = 0;
Observable.Defer(() => { 
   return ((++attempt == 1)  ? source : source.DelaySubscription(TimeSpan.FromSeconds(1)))
})
.Retry(4)
.Select(response => ...)

只是停留了第二个是不是一个很好的方法,重试,虽然让我们改变常数为接收的重试次数,并返回一个适当的延时功能。指数回退是很容易实现的。

Just pausing for a second isn't a very good retry method though so let's change that constant to be a function which receives the retry count and returns an appropriate delay. Exponential back off is easy enough to implement.

Func<int, TimeSpan> strategy = n => TimeSpan.FromSeconds(Math.Pow(n, 2));

((++attempt == 1)  ? source : source.DelaySubscription(strategy(attempt - 1)))

我们现在几乎完成,我们只需要添加指定的,我们应该重新尝试哪些异常的一种方式。让我们添加一个函数,给定一个异常返回是否是有意义的重试,我们把它叫做retryOnError。

We're almost done now, we just need to add a way of specifying for which exceptions we should retry. Let's add a function that given an exception returns whether or not it makes sense to retry, we'll call it retryOnError.

现在,我们需要编写一些可怕看code,但我承担。

Now we need to write some scary looking code but bear with me.

Observable.Defer(() => {
    return ((++attempt == 1)  ? source : source.DelaySubscription(strategy(attempt - 1)))
        .Select(item => new Tuple<bool, WebResponse, Exception>(true, item, null))
        .Catch<Tuple<bool, WebResponse, Exception>, Exception>(e => retryOnError(e)
            ? Observable.Throw<Tuple<bool, WebResponse, Exception>>(e)
            : Observable.Return(new Tuple<bool, WebResponse, Exception>(false, null, e)));
})
.Retry(retryCount)
.SelectMany(t => t.Item1
    ? Observable.Return(t.Item2)
    : Observable.Throw<T>(t.Item3))

所有这些尖括号在那里元帅,我们不应该重试过去 .Retry()例外。我们所做的内部观察的是的IObservable&LT元组LT; BOOL,WebResponse类,异常&GT;&GT; 其中第一个布尔表示,如果我们有一个响应或异常。如果retryOnError表明我们应该重试特定的异常内观察到的将抛出,这将通过重试回升。在刚刚的SelectMany我们解开元组,使所产生的可观察到的是的IObservable&LT; WebRequest的方式&gt; 再次

All of those angle brackets are there to marshal an exception for which we shouldn't retry past the .Retry(). We've made the inner observable be an IObservable<Tuple<bool, WebResponse, Exception>> where the first bool indicates if we have a response or an exception. If retryOnError indicates that we should retry for a particular exception the inner observable will throw and that will be picked up by the retry. The SelectMany just unwraps our Tuple and makes the resulting observable be IObservable<WebRequest> again.

请参阅我的全部源和,在最终版本的测试依据。有了这个操作符可以让我们写我们重试code相当简洁

See my gist with full source and tests for the final version. Having this operator allows us to write our retry code quite succinctly

Observable.Defer(() => SomApiMethod())
  .RetryWithBackoffStrategy(
     retryCount: 4, 
     retryOnError: e => e is ApiRetryWebException
  )

这篇关于写一个Rx和QUOT; RetryAfter&QUOT;扩展方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆