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(),延迟也存在。如果延迟不是在发生错误后而是在重试之前应用(显然,不是在第一次尝试之前),我会更好。
您可以使用
下列类包含重试逻辑:
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所说的
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(我必须将
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); } } }); } }); } |
通过将
该代码在我的GitHub上可用,并带有以下测试用例:
请参见
与来自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重试。替换
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) } } } } |
然后在可观察的
您可以在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) { } }); |