关于rx java:rxjava:我可以使用retry()但有延迟吗?

rxjava: Can I use retry() but with delay?

我在Android应用中使用rxjava异步处理网络请求。现在,我只想在一段时间后重试失败的网络请求。

有什么方法可以在Observable上使用retry(),但是只能在特定延迟后重试?

有没有办法让Observable知道当前正在重试(而不是第一次尝试)?

我看了看debounce()/ throttleWithTimeout(),但他们似乎在做些不同的事情。

编辑:

我想我找到了一种方法来做,但是我想对确认这是正确的方法还是其他更好的方法感兴趣。

我正在做的是:在Observable.OnSubscribe的call()方法中,在调用Subscribers onError()方法之前,我只是让线程休眠了所需的时间。因此,要每1000毫秒重试一次,我会执行以下操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public void call(Subscriber<? super List<ProductNode>> subscriber) {
    try {
        Log.d(TAG,"trying to load all products with pid:" + pid);
        subscriber.onNext(productClient.getProductNodesForParentId(pid));
        subscriber.onCompleted();
    } catch (Exception e) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e1) {
            e.printStackTrace();
        }
        subscriber.onError(e);
    }
}

由于此方法始终在IO线程上运行,因此它不会阻止UI。我能看到的唯一问题是,即使第一个错误也被延迟报告,所以即使没有retry(),延迟也存在。如果延迟不是在发生错误后而是在重试之前应用(显然,不是在第一次尝试之前),我会更好。


您可以使用retryWhen()运算符将重试逻辑添加到任何Observable中。

下列类包含重试逻辑:

RxJava 2.x

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class RetryWithDelay implements Function<Observable<? extends Throwable>, Observable< ? >> {
    private final int maxRetries;
    private final int retryDelayMillis;
    private int retryCount;

    public RetryWithDelay(final int maxRetries, final int retryDelayMillis) {
        this.maxRetries = maxRetries;
        this.retryDelayMillis = retryDelayMillis;
        this.retryCount = 0;
    }

    @Override
    public Observable< ? > apply(final Observable<? extends Throwable> attempts) {
        return attempts
                .flatMap(new Function<Throwable, Observable< ? >>() {
                    @Override
                    public Observable< ? > apply(final Throwable throwable) {
                        if (++retryCount < maxRetries) {
                            // When this Observable calls onNext, the original
                            // Observable will be retried (i.e. re-subscribed).
                            return Observable.timer(retryDelayMillis,
                                    TimeUnit.MILLISECONDS);
                        }

                        // Max retries hit. Just pass the error along.
                        return Observable.error(throwable);
                    }
                });
    }
}

RxJava 1.x

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class RetryWithDelay implements
        Func1<Observable<? extends Throwable>, Observable< ? >> {

    private final int maxRetries;
    private final int retryDelayMillis;
    private int retryCount;

    public RetryWithDelay(final int maxRetries, final int retryDelayMillis) {
        this.maxRetries = maxRetries;
        this.retryDelayMillis = retryDelayMillis;
        this.retryCount = 0;
    }

    @Override
    public Observable< ? > call(Observable<? extends Throwable> attempts) {
        return attempts
                .flatMap(new Func1<Throwable, Observable< ? >>() {
                    @Override
                    public Observable< ? > call(Throwable throwable) {
                        if (++retryCount < maxRetries) {
                            // When this Observable calls onNext, the original
                            // Observable will be retried (i.e. re-subscribed).
                            return Observable.timer(retryDelayMillis,
                                    TimeUnit.MILLISECONDS);
                        }

                        // Max retries hit. Just pass the error along.
                        return Observable.error(throwable);
                    }
                });
    }
}

用法:

1
2
3
4
// Add retry logic to existing observable.
// Retry max of 3 times with a delay of 2 seconds.
observable
    .retryWhen(new RetryWithDelay(3, 2000));


受到Paul的回答的启发,如果您不关心Abhijit Sarkar所说的retryWhen问题,无条件地延迟用rxJava2重新订阅的最简单方法是:

1
source.retryWhen(throwables -> throwables.delay(1, TimeUnit.SECONDS))

您可能希望查看有关retryWhen和repeatWhen的更多示例和说明。


此示例适用于jxjava 2.2.2:

立即重试:

1
2
3
4
Single.just(somePaylodData)
   .map(data -> someConnection.send(data))
   .retry(5)
   .doOnSuccess(status -> log.info("Yay! {}", status);

延迟重试:

1
2
3
4
5
6
7
Single.just(somePaylodData)
   .map(data -> someConnection.send(data))
   .retryWhen((Flowable<Throwable> f) -> f.take(5).delay(300, TimeUnit.MILLISECONDS))
   .doOnSuccess(status -> log.info("Yay! {}", status)
   .doOnError((Throwable error)
                -> log.error("I tried five times with a 300ms break"
                             +" delay in between. But it was in vain."));

如果someConnection.send()失败,则源单失败。
当发生这种情况时,retryWhen内部的可观察到的失败将发出错误。
我们将发射延迟300毫秒,然后将其发送回以发出重试信号。
take(5)保证我们的可观察信号在收到五个错误后将终止。
retryWhen看到终止并且在第五次失败后不重试。


这是基于我所看到的Ben Christensen的代码片段,RetryWhen Example和RetryWhenTestsConditional(我必须将n.getThrowable()更改为n才能运行)的解决方案。我使用evant / gradle-retrolambda使lambda表示法在Android上可以使用,但是您不必使用lambdas(尽管强烈建议使用)。对于延迟,我实现了指数补偿,但是您可以在此处插入所需的补偿逻辑。为了完整起见,我添加了subscribeOnobserveOn运算符。我正在为AndroidSchedulers.mainThread()使用ReactiveX / RxAndroid。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int ATTEMPT_COUNT = 10;

public class Tuple<X, Y> {
    public final X x;
    public final Y y;

    public Tuple(X x, Y y) {
        this.x = x;
        this.y = y;
    }
}


observable
    .subscribeOn(Schedulers.io())
    .retryWhen(
            attempts -> {
                return attempts.zipWith(Observable.range(1, ATTEMPT_COUNT + 1), (n, i) -> new Tuple<Throwable, Integer>(n, i))
                .flatMap(
                        ni -> {
                            if (ni.y > ATTEMPT_COUNT)
                                return Observable.error(ni.x);
                            return Observable.timer((long) Math.pow(2, ni.y), TimeUnit.SECONDS);
                        });
            })
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(subscriber);


而不是使用MyRequestObservable.retry我使用包装函数retryObservable(MyRequestObservable,retrycount,seconds)返回一个新的Observable来处理延迟的间接操作,所以我可以

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
retryObservable(restApi.getObservableStuff(), 3, 30)
    .subscribe(new Action1<BonusIndividualList>(){
        @Override
        public void call(BonusIndividualList arg0)
        {
            //success!
        }
    },
    new Action1<Throwable>(){
        @Override
        public void call(Throwable arg0) {
           // failed after the 3 retries !
        }});


// wrapper code
private static < T > Observable< T > retryObservable(
        final Observable< T > requestObservable, final int nbRetry,
        final long seconds) {

    return Observable.create(new Observable.OnSubscribe< T >() {

        @Override
        public void call(final Subscriber<? super T> subscriber) {
            requestObservable.subscribe(new Action1< T >() {

                @Override
                public void call(T arg0) {
                    subscriber.onNext(arg0);
                    subscriber.onCompleted();
                }
            },

            new Action1<Throwable>() {
                @Override
                public void call(Throwable error) {

                    if (nbRetry > 0) {
                        Observable.just(requestObservable)
                                .delay(seconds, TimeUnit.SECONDS)
                                .observeOn(mainThread())
                                .subscribe(new Action1<Observable< T >>(){
                                    @Override
                                    public void call(Observable< T > observable){
                                        retryObservable(observable,
                                                nbRetry - 1, seconds)
                                                .subscribe(subscriber);
                                    }
                                });
                    } else {
                        // still fail after retries
                        subscriber.onError(error);
                    }

                }
            });

        }

    });

}


retryWhen是一个复杂的操作员,甚至可能是越野车。官方文档和至少一个答案在这里使用range运算符,如果不进行任何重试,该运算符将失败。请参阅我与ReactiveX成员David Karnok的讨论。

通过将flatMap更改为concatMap并添加RetryDelayStrategy类,我改进了kjones的答案。 flatMap不能保留发射顺序,而concatMap可以保留,这对于具有退避的延迟很重要。顾名思义,RetryDelayStrategy让我们从生成重试延迟的各种模式中进行选择,包括回退。
该代码在我的GitHub上可用,并带有以下测试用例:

  • 第一次尝试成功(无重试)
  • 重试1次后失败
  • 尝试重试3次,但第二次成功,因此不会第三次重试
  • 第三次重试成功
  • 请参见setRandomJokes方法。


    与来自kjones的答案相同,但已更新至最新版本
    对于RxJava 2.x版本:('io.reactivex.rxjava2:rxjava:2.1.3')

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    public class RetryWithDelay implements Function<Flowable<Throwable>, Publisher< ? >> {

        private final int maxRetries;
        private final long retryDelayMillis;
        private int retryCount;

        public RetryWithDelay(final int maxRetries, final int retryDelayMillis) {
            this.maxRetries = maxRetries;
            this.retryDelayMillis = retryDelayMillis;
            this.retryCount = 0;
        }

        @Override
        public Publisher< ? > apply(Flowable<Throwable> throwableFlowable) throws Exception {
            return throwableFlowable.flatMap(new Function<Throwable, Publisher< ? >>() {
                @Override
                public Publisher< ? > apply(Throwable throwable) throws Exception {
                    if (++retryCount < maxRetries) {
                        // When this Observable calls onNext, the original
                        // Observable will be retried (i.e. re-subscribed).
                        return Flowable.timer(retryDelayMillis,
                                TimeUnit.MILLISECONDS);
                    }

                    // Max retries hit. Just pass the error along.
                    return Flowable.error(throwable);
                }
            });
        }
    }

    用法:

    //向现有可观察对象添加重试逻辑。
    //重试最多3次,延迟2秒。

    1
    2
    observable
        .retryWhen(new RetryWithDelay(3, 2000));

    现在,在RxJava版本1.0+中,您可以使用zipWith延迟实现重试。

    为kjones答案添加修改。

    改性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    public class RetryWithDelay implements
                                Func1<Observable<? extends Throwable>, Observable< ? >> {

        private final int MAX_RETRIES;
        private final int DELAY_DURATION;
        private final int START_RETRY;

        /**
         * Provide number of retries and seconds to be delayed between retry.
         *
         * @param maxRetries             Number of retries.
         * @param delayDurationInSeconds Seconds to be delays in each retry.
         */
        public RetryWithDelay(int maxRetries, int delayDurationInSeconds) {
            MAX_RETRIES = maxRetries;
            DELAY_DURATION = delayDurationInSeconds;
            START_RETRY = 1;
        }

        @Override
        public Observable< ? > call(Observable<? extends Throwable> observable) {
            return observable
                    .delay(DELAY_DURATION, TimeUnit.SECONDS)
                    .zipWith(Observable.range(START_RETRY, MAX_RETRIES),
                             new Func2<Throwable, Integer, Integer>() {
                                 @Override
                                 public Integer call(Throwable throwable, Integer attempt) {
                                      return attempt;
                                 }
                             });
        }
    }

    基于kjones的答案是延迟的Kotlin版本的RxJava 2.x重试。替换ObservableFlowable创建相同的扩展名。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    fun < T > Observable< T >.retryWithDelay(maxRetries: Int, retryDelayMillis: Int): Observable< T > {
        var retryCount = 0

        return retryWhen { thObservable ->
            thObservable.flatMap { throwable ->
                if (++retryCount < maxRetries) {
                    Observable.timer(retryDelayMillis.toLong(), TimeUnit.MILLISECONDS)
                } else {
                    Observable.error(throwable)
                }
            }
        }
    }

    然后在可观察的observable.retryWithDelay(3, 1000)上使用它


    您可以在retryWhen运算符中返回的Observable中添加延迟

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
              /**
     * Here we can see how onErrorResumeNext works and emit an item in case that an error occur in the pipeline and an exception is propagated
     */
    @Test
    public void observableOnErrorResumeNext() {
        Subscription subscription = Observable.just(null)
                                              .map(Object::toString)
                                              .doOnError(failure -> System.out.println("Error:" + failure.getCause()))
                                              .retryWhen(errors -> errors.doOnNext(o -> count++)
                                                                         .flatMap(t -> count > 3 ? Observable.error(t) : Observable.just(null).delay(100, TimeUnit.MILLISECONDS)),
                                                         Schedulers.newThread())
                                              .onErrorResumeNext(t -> {
                                                  System.out.println("Error after all retries:" + t.getCause());
                                                  return Observable.just("I save the world for extinction!");
                                              })
                                              .subscribe(s -> System.out.println(s));
        new TestSubscriber((Observer) subscription).awaitTerminalEvent(500, TimeUnit.MILLISECONDS);
    }

    您可以在此处查看更多示例。 https://github.com/politrons/reactive


    如果您需要打印重试计数,
    您可以使用Rxjava Wiki页面https://github.com/ReactiveX/RxJava/wiki/Error-Handling-Operators中提供的示例

    1
    2
    3
    4
    5
    6
    7
    8
    observable.retryWhen(errors ->
        // Count and increment the number of errors.
        errors.map(error -> 1).scan((i, j) -> i + j)  
           .doOnNext(errorCount -> System.out.println(" -> query errors #:" + errorCount))
           // Limit the maximum number of retries.
           .takeWhile(errorCount -> errorCount < retryCounts)  
           // Signal resubscribe event after some delay.
           .flatMapSingle(errorCount -> Single.timer(errorCount, TimeUnit.SECONDS));

    (Kotlin)我通过使用指数退避和Observable.range()的应用防御发射改进了代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
        fun testOnRetryWithDelayExponentialBackoff() {
        val interval = 1
        val maxCount = 3
        val ai = AtomicInteger(1);
        val source = Observable.create<Unit> { emitter ->
            val attempt = ai.getAndIncrement()
            println("Subscribe ${attempt}")
            if (attempt >= maxCount) {
                emitter.onNext(Unit)
                emitter.onComplete()
            }
            emitter.onError(RuntimeException("Test $attempt"))
        }

        // Below implementation of"retryWhen" function, remove all"println()" for real code.
        val sourceWithRetry: Observable<Unit> = source.retryWhen { throwableRx ->
            throwableRx.doOnNext({ println("Error: $it") })
                    .zipWith(Observable.range(1, maxCount)
                            .concatMap { Observable.just(it).delay(0, TimeUnit.MILLISECONDS) },
                            BiFunction { t1: Throwable, t2: Int -> t1 to t2 }
                    )
                    .flatMap { pair ->
                        if (pair.second >= maxCount) {
                            Observable.error(pair.first)
                        } else {
                            val delay = interval * 2F.pow(pair.second)
                            println("retry delay: $delay")
                            Observable.timer(delay.toLong(), TimeUnit.SECONDS)
                        }
                    }
        }

        //Code to print the result in terminal.
        sourceWithRetry
                .doOnComplete { println("Complete") }
                .doOnError({ println("Final Error: $it") })
                .blockingForEach { println("$it") }
    }

    对于Kotlin和RxJava1版本

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class RetryWithDelay(private val MAX_RETRIES: Int, private val DELAY_DURATION_IN_SECONDS: Long)
        : Function1<Observable<out Throwable>, Observable<*>> {

        private val START_RETRY: Int = 1

        override fun invoke(observable: Observable<out Throwable>): Observable<*> {
            return observable.delay(DELAY_DURATION_IN_SECONDS, TimeUnit.SECONDS)
                .zipWith(Observable.range(START_RETRY, MAX_RETRIES),
                    object : Function2<Throwable, Int, Int> {
                        override fun invoke(throwable: Throwable, attempt: Int): Int {
                            return attempt
                        }
                    })
        }
    }

    只需这样做:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
                      Observable.just("")
                                .delay(2, TimeUnit.SECONDS) //delay
                                .flatMap(new Func1<String, Observable<File>>() {
                                    @Override
                                    public Observable<File> call(String s) {
                                        L.from(TAG).d("postAvatar=");

                                        File file = PhotoPickUtil.getTempFile();
                                        if (file.length() <= 0) {
                                            throw new NullPointerException();
                                        }
                                        return Observable.just(file);
                                    }
                                })
                                .retry(6)
                                .subscribe(new Action1<File>() {
                                    @Override
                                    public void call(File file) {
                                        postAvatar(file);
                                    }
                                }, new Action1<Throwable>() {
                                    @Override
                                    public void call(Throwable throwable) {

                                    }
                                });