rxjava: Tôi có thể sử dụng retry () nhưng có độ trễ không?


91

Tôi đang sử dụng rxjava trong ứng dụng Android của mình để xử lý các yêu cầu mạng không đồng bộ. Bây giờ tôi chỉ muốn thử lại một yêu cầu mạng không thành công sau khi một thời gian nhất định trôi qua.

Có cách nào để sử dụng retry () trên một Observable nhưng chỉ thử lại sau một khoảng thời gian nhất định không?

Có cách nào để cho Người quan sát biết hiện đang được thử lại (thay vì thử lần đầu tiên) không?

Tôi đã xem xét debounce () / TuettleWithTimeout () nhưng chúng dường như đang làm điều gì đó khác.

Biên tập:

Tôi nghĩ rằng tôi đã tìm thấy một cách để làm điều đó, nhưng tôi muốn xác nhận rằng đây là cách chính xác để làm điều đó hoặc những cách khác tốt hơn.

Những gì tôi đang làm là thế này: Trong phương thức call () của Observable.OnSubscribe của tôi, trước khi tôi gọi phương thức Subscriber onError (), tôi chỉ cần để Thread ngủ trong khoảng thời gian mong muốn. Vì vậy, để thử lại sau mỗi 1000 mili giây, tôi làm như sau:

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

Vì phương pháp này đang chạy trên một luồng IO nên nó không chặn giao diện người dùng. Vấn đề duy nhất tôi có thể thấy là ngay cả lỗi đầu tiên được báo cáo với độ trễ, vì vậy độ trễ vẫn có ngay cả khi không có thử lại (). Tôi muốn tốt hơn nếu độ trễ không được áp dụng sau một lỗi mà thay vào đó là trước khi thử lại (nhưng không phải trước lần thử đầu tiên, rõ ràng là).

Câu trả lời:


169

Bạn có thể sử dụng retryWhen()toán tử để thêm logic thử lại vào bất kỳ Observable nào.

Lớp sau chứa logic thử lại:

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

Sử dụng:

// 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 tôi đã cùng một vấn đề, sự thay đổi RetryWithDelaynày: pastebin.com/6SiZeKnC
user1480019

2
Có vẻ như toán tử RxJava retryWhen đã thay đổi kể từ khi tôi viết điều này ban đầu. Tôi sẽ nhận được câu trả lời được cập nhật.
kjones

3
Bạn nên cập nhật câu trả lời này để tuân thủ RxJava 2
Vishnu M.

1
phiên bản rxjava 2 sẽ tìm kiếm kotlin như thế nào?
Gabriel Sanmartin


14

Ví dụ này hoạt động với jxjava 2.2.2:

Thử lại ngay lập tức:

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

Thử lại với độ trễ:

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

Đơn nguồn của chúng tôi không thành công nếu someConnection.send () không thành công. Khi điều đó xảy ra, có thể quan sát được các lỗi bên trong thử lại Khi phát ra lỗi. Chúng tôi trì hoãn phát xạ đó đi 300ms và gửi lại để báo hiệu thử lại. (5) đảm bảo rằng tín hiệu có thể quan sát được của chúng tôi sẽ chấm dứt sau khi chúng tôi nhận được năm lỗi. Thử lại Khi thấy kết thúc và không thử lại sau lần thất bại thứ năm.


9

Đây là một giải pháp dựa trên các đoạn trích của Ben Christensen mà tôi đã xem, RetryWhen ExampleRetryWhenTestsConditional (tôi đã phải thay đổi n.getThrowable()để nnó hoạt động). Tôi đã sử dụng evant / gradle-retrolambda để làm cho ký hiệu lambda hoạt động trên Android, nhưng bạn không cần phải sử dụng lambdas (mặc dù nó rất được khuyến khích). Đối với sự chậm trễ, tôi đã triển khai back-off theo cấp số nhân, nhưng bạn có thể cắm những gì bạn muốn ở đó logic backoff. Để hoàn thiện, tôi đã thêm toán tử subscribeOnobserveOn. Tôi đang sử dụng ReactiveX / RxAndroid cho 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
cái này trông thanh lịch nhưng tôi không sử dụng các hàm lamba, làm thế nào tôi có thể viết mà không có lambas? @ amitai-hoze
ericn

Ngoài ra, làm cách nào để viết nó sao cho tôi có thể sử dụng lại hàm thử lại này cho các Observableđối tượng khác ?
ericn

nevermind, tôi đã sử dụng kjonesgiải pháp và nó làm việc ra hoàn hảo cho tôi, nhờ
ericn

8

thay vì sử dụng MyRequestObservable.retry, tôi sử dụng một hàm wrapper retryObservable (MyRequestObservable, retrycount, seconds) trả về một Observable mới xử lý chuyển hướng cho độ trễ để tôi có thể làm

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

                }
            });

        }

    });

}

Tôi thực sự xin lỗi vì đã không trả lời sớm hơn - bằng cách nào đó tôi đã bỏ lỡ thông báo từ SO rằng đã có câu trả lời cho câu hỏi của tôi ... Tôi đã ủng hộ phản ứng của bạn vì tôi thích ý tưởng đó, nhưng tôi không chắc liệu - theo nguyên tắc của SO - Tôi nên chấp nhận câu trả lời vì nó là một cách giải quyết hơn là một câu trả lời trực tiếp. Nhưng tôi đoán, vì bạn đang đưa ra một cách giải quyết nên câu trả lời cho câu hỏi ban đầu của tôi là "không, bạn không thể" ...
david.mihola

5

retryWhenlà một toán tử phức tạp, thậm chí có thể có lỗi. Tài liệu chính thức và ít nhất một câu trả lời ở đây sử dụng rangetoán tử, sẽ không thành công nếu không có lần thử lại nào được thực hiện. Xem cuộc thảo luận của tôi với thành viên David Karnok của ReactiveX.

Tôi được cải thiện khi trả lời kjones' bằng cách thay đổi flatMapđể concatMapvà bằng cách thêm một RetryDelayStrategylớp. flatMapkhông bảo toàn thứ tự phát xạ trong khi concatMapđó, điều này rất quan trọng đối với sự chậm trễ khi dự phòng. Các RetryDelayStrategy, như tên cho thấy, chúng ta hãy cho người dùng lựa chọn các chế độ khác nhau tạo ra sự chậm trễ retry, bao gồm back-off. Mã có sẵn trên GitHub của tôi hoàn chỉnh với các trường hợp thử nghiệm sau:

  1. Thành công trong lần thử đầu tiên (không thử lại)
  2. Không thành công sau 1 lần thử lại
  3. Cố gắng thử lại 3 lần nhưng thành công ở lần thứ 2 do đó không thử lại lần 3
  4. Thành công khi thử lại lần thứ 3

Xem setRandomJokesphương pháp.


3

Giờ đây với phiên bản RxJava 1.0+, bạn có thể sử dụng zipWith để thử lại với độ trễ.

Thêm sửa đổi cho câu trả lời kjones .

Đã sửa đổi

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

Câu trả lời tương tự như từ kjones nhưng được cập nhật lên phiên bản mới nhất Đối với phiên bản RxJava 2.x : ('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);
            }
        });
    }
}

Sử dụng:

// Thêm logic thử lại vào quan sát hiện có. // Thử lại tối đa 3 lần với độ trễ là 2 giây.

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

3

Dựa trên câu trả lời kjones ở đây là phiên bản Kotlin của RxJava 2.x thử lại với độ trễ dưới dạng phần mở rộng. Thay thế Observableđể tạo cùng một phần mở rộng cho 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)
            }
        }
    }
}

Sau đó, chỉ cần sử dụng nó trên observable.retryWithDelay(3, 1000)


Có thể thay thế cái này bằng Singletốt không?
Papps

2
@Papps Vâng điều đó sẽ hoạt động, chỉ cần lưu ý rằng flatMapsẽ phải sử dụng Flowable.timerFlowable.error mặc dù chức năng là như vậy Single<T>.retryWithDelay.
JuliusScript

1

Bạn có thể thêm độ trễ trong trả về Quan sát được trong phép thử lại Khi toán tử

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

Bạn có thể xem thêm các ví dụ khác tại đây. https://github.com/politrons/reactive


0

Chỉ cần làm như thế này:

                  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

Đối với phiên bản Kotlin & RxJava1

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) Tôi đã cải thiện một chút mã với backoff theo cấp số nhân và áp dụng phát biện pháp bảo vệ của Observable.range ():

    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

trong trường hợp bạn cần in ra số lần thử lại, bạn có thể sử dụng ví dụ được cung cấp trong trang wiki của Rxjava 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));
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.