rxjava: retry () 'yi gecikmeli olarak kullanabilir miyim?


93

Ağ isteklerini eşzamansız olarak işlemek için Android uygulamamda rxjava kullanıyorum. Şimdi, başarısız olan bir ağ isteğini yalnızca belirli bir süre geçtikten sonra yeniden denemek istiyorum.

Bir Gözlemlenebilir'de retry () kullanmanın ancak belirli bir gecikmeden sonra yeniden denemenin bir yolu var mı?

Gözlemlenebilir'in şu anda yeniden denenmekte olduğunu bildirmenin bir yolu var mı (ilk kez denenmenin aksine)?

Debounce () / throttleWithTimeout () 'a baktım ama farklı bir şey yapıyor gibi görünüyorlar.

Düzenle:

Sanırım bunu yapmanın bir yolunu buldum, ancak bunun doğru bir yol olduğunu ya da başka, daha iyi yollar olduğunu doğrulamakla ilgileniyorum.

Yaptığım şey şudur: Observable.OnSubscribe'ımın call () yönteminde, Subscribers onError () yöntemini çağırmadan önce, Thread'ın istenen süre boyunca uyumasına izin veriyorum. Yani, her 1000 milisaniyede bir yeniden denemek için şuna benzer bir şey yapıyorum:

@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);
    }
}

Bu yöntem bir GÇ iş parçacığında çalıştığından, kullanıcı arabirimini engellemez. Görebildiğim tek sorun, ilk hatanın bile gecikmeli olarak bildirilmesidir, bu nedenle, yeniden deneme () olmasa bile gecikme vardır. Gecikme uygulanmadı ben daha iyi istiyorum sonra bir hata ancak bunun yerine önce (besbelli, ama ilk denemede öncesi) yeniden deneme.

Yanıtlar:


173

retryWhen()Herhangi bir Gözlemlenebilir'e yeniden deneme mantığı eklemek için operatörü kullanabilirsiniz .

Aşağıdaki sınıf, yeniden deneme mantığını içerir:

RxJava 2.x

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

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);
                    }
                });
    }
}

Kullanım:

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

2
Error:(73, 20) error: incompatible types: RetryWithDelay cannot be converted to Func1<? super Observable<? extends Throwable>,? extends Observable<?>>
Nima G

3
@nima Ben de aynı problemi yaşadım, bunu değiştir RetryWithDelay: pastebin.com/6SiZeKnC
user1480019

2
Görünüşe göre RxJava retryWhen operatör bunu yazdığımdan beri değişti. Cevabı güncelleyeceğim.
kjones

3
Bu yanıtı, RxJava 2
Vishnu M.

1
rxjava 2 versiyonu kotlin'i nasıl arar?
Gabriel Sanmartin


15

Bu örnek jxjava 2.2.2 ile çalışır:

Gecikmeden yeniden deneyin:

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

Gecikmeli yeniden deneyin:

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 () başarısız olursa tek kaynağımız başarısız olur. Bu olduğunda, hatayı yaydığında retryWhen içindeki arızalar gözlemlenebilir. Bu emisyonu 300 ms geciktirir ve yeniden deneme sinyali vermek için geri göndeririz. take (5), gözlemlenebilir sinyalimizin beş hata aldıktan sonra sona ereceğini garanti eder. retry: Sonlandırmayı gördüğünde ve beşinci hatadan sonra tekrar denemediğinde


9

Bu, Ben Christensen'in gördüğüm, RetryWhen Example ve RetryWhenTestsConditional snippet'lerine dayanan bir çözümdür ( çalışması için değiştirmem n.getThrowable()gerekiyordu n). Kullandığım Evant / gradle-retrolambda Android'de lambda notasyonu çalışması için, ama (bunu öneriyor rağmen) Eğer lambdas kullanmak zorunda değilsiniz. Gecikme için üstel geri çekmeyi uyguladım, ancak orada istediğiniz geri çekilme mantığını takabilirsiniz. Tamlık için subscribeOnve observeOnoperatörlerini ekledim . Ben kullanıyorum ReactiveX / RxAndroid için AndroidSchedulers.mainThread().

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);

2
bu zarif görünüyor ama lamba fonksiyonlarını kullanmıyorum, lambas olmadan nasıl yazabilirim? @ amitai-hoze
ericn

Ayrıca, bu yeniden deneme işlevini diğer Observablenesneler için yeniden kullanabilmek için nasıl yazabilirim ?
ericn

boşver, kjonesçözümü kullandım ve benim için mükemmel çalışıyor, teşekkürler
ericn

8

MyRequestObservable.retry kullanmak yerine, retryObservable (MyRequestObservable, retrycount, seconds) için bir sarmalayıcı işlevi kullanıyorum ve bunu yapabilmem için gecikmenin indirilmesini işleyen yeni bir Gözlemlenebilir

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);
                    }

                }
            });

        }

    });

}

Daha önce yanıt vermediğim için çok üzgünüm - bir şekilde SO'dan soruma bir yanıt geldiğine dair bildirimi kaçırdım ... Cevabınızı bu fikri beğendiğim için ekledim, ancak SO ilkelerine göre mi emin değilim - Doğrudan bir cevaptan çok geçici bir çözüm olduğu için cevabı kabul etmeliyim. Ama sanırım, geçici bir çözüm
sunduğunuz

5

retryWhenkarmaşık, hatta hatalı bir operatördür. Resmi belge ve buradaki en az bir cevap rangeoperatörü kullanır ve yapılacak yeniden deneme yoksa başarısız olur. ReactiveX üyesi David Karnok ile yaptığım tartışmaya bakın .

Ben değiştirerek kjones' cevabı üzerine geliştirilmiş flatMapetmek concatMapve bir ekleyerek RetryDelayStrategysınıfı. flatMapemisyon sırasını concatMapkorumaz, bu da geri çekilmedeki gecikmeler için önemlidir. RetryDelayStrategyAdından da anlaşılacağı gibi, let kullanıcı geri-off dahil üreten yeniden deneme gecikmeler çeşitli modları arasından seçim var. Kod, aşağıdaki test durumlarıyla birlikte GitHub'ımda mevcuttur :

  1. 1. denemede başarılı (yeniden deneme yok)
  2. 1 yeniden denemeden sonra başarısız olur
  3. 3 kez yeniden denemeyi dener, ancak 2.'de başarılı olur, bu nedenle 3. kez tekrar denemez
  4. 3. yeniden denemede başarılı oldu

setRandomJokesYönteme bakın .


4

Burada kjones cevabına dayanarak, RxJava 2.x'in Kotlin sürümü, uzantı olarak bir gecikmeyle yeniden denemedir. İçin Observableaynı uzantıyı oluşturmak için değiştirin Flowable.

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)
            }
        }
    }
}

O zaman sadece gözlemlenebilir üzerinde kullan observable.retryWithDelay(3, 1000)


Bunu da değiştirmek mümkün mü Single?
Papps

2
@Papps Evet bu, bu sadece not çalışmalıdır flatMaporada kullanmak zorunda olacak Flowable.timerve Flowable.error fonksiyonu olsa Single<T>.retryWithDelay.
JuliusScript

3

Artık RxJava sürüm 1.0+ ile, gecikmeli olarak yeniden denemeyi gerçekleştirmek için zipWith'i kullanabilirsiniz.

Kjones cevabına değişiklikler ekleniyor .

Değiştirilmiş

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;
                             }
                         });
    }
}

3

Kjones ile aynı yanıt ancak en son sürüme güncellendi RxJava 2.x sürümü için: ('io.reactivex.rxjava2: rxjava: 2.1.3')

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);
            }
        });
    }
}

Kullanım:

// Mevcut gözlemlenebilirliğe yeniden deneme mantığı ekleyin. // 2 saniye gecikmeyle en fazla 3 kez yeniden deneyin.

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

1

Yeniden denemede döndürülen Gözlemlenebilir'e bir gecikme ekleyebilirsiniz.

          /**
 * 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);
}

Burada daha fazla örnek görebilirsiniz. https://github.com/politrons/reactive


0

Basitçe şöyle yapın:

                  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) {

                                }
                            });

0

Kotlin & RxJava1 versiyonu için

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
                    }
                })
    }
}

0

(Kotlin) Üstel geri çekilme ile kodu biraz iyileştirdim ve Observable.range () 'ın savunma yaymasını uyguladım:

    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") }
}

0

yeniden deneme sayısını yazdırmanız gerektiğinde, Rxjava'nın wiki sayfasında sağlanan örneği kullanabilirsiniz https://github.com/ReactiveX/RxJava/wiki/Error-Handling-Operators

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));
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.