retryWhen with timer似乎颠覆了合并行为

时间:2017-01-31 16:10:49

标签: java-8 rx-java

我在异步消息传递环境(vert.x)中使用RxJava,因此有一个如下所示的流程:

Observable.defer( () -> getEndpoint() )
          .mergeWith( getCancellationMessage() )
          .flatMap( endpoint -> useEndpoint( endpoint ) )
          .retryWhen( obs -> obs.flatMap( error -> {
              if ( wasCancelled( error ) ) {
                  return Observable.error( error );
              }
              return Observable.timer(/* args */)
          }) )
          .subscribe( result -> useResult( result ),
                      error -> handleError( error )
          );

getCancellationMessage()的实现返回一个可观察的流,只要从独立的消息源收到取消消息,就会发出错误。此流永远不会发出Observable.error()以外的任何内容,只会在收到取消消息时发出错误。

如果我了解merge的工作原理,那么当onError发出错误时,应通过getCancellationMessage()终止整个链。

但是,我发现如果retryWhen运算符在收到取消消息时正在等待定时器发出,则忽略该错误并继续retryWhen循环,就好像取消永远不会接收。

我可以通过将Observable.timer()getCancellationMessage()函数合并来修复行为,但我不明白为什么我必须首先执行此操作。

预期会发生merge / retryWhen互动吗?

编辑:

以下是getCancellationMessage()函数正在执行的操作的示例:

Observable<T> getCancellationMessage() {
    if ( this.messageStream == null ) {
       this.messageStream = this.messageConsumer.toObservable()
                            .flatMap( message -> {
                                this.messageConsumer.unregister();
                                if ( isCancelMessage(message) ) {
                                    return Observable.error( new CancelError() );
                                }
                                else {
                                    return Observable.error( new FatalError() );
                                }
                            });
    }
    return this.messageStream;
}

请注意,我不拥有this.messageConsumer的实现 - 这来自我正在使用的第三方库(vert.x),所以我不控制该Observable的实现。

根据我的理解,messageConsumer.toObservable()方法会返回Observable.create()的结果,该结果随this class实例提供,只要有新消息,它就会调用订阅者的onNext方法has arrived

messageConsumer.unregister()的呼叫会阻止接收任何其他消息。

1 个答案:

答案 0 :(得分:2)

  

但是,我发现如果retryWhen运算符在收到取消消息时等待定时器发出,则忽略该错误并且retryWhen循环继续,就像从未接收到取消一样。

运算符retryWhen将上游Throwable转换为值并将其路由到您提供的序列,以获取值响应以重试上游或结束流,因此

Observable.error(new IOException())
.retryWhen((Observable<Throwable> error) -> error)
.subscribe();

将无限期重试,因为内部error现在被视为值,而不是例外。

retryWhen本身并不知道哪个error值应该被认为是不应该重试的值,这是你内心的工作流速:

Observable.defer( () -> getEndpoint() )
  .mergeWith( getCancellationMessage() )
  .flatMap( endpoint -> useEndpoint( endpoint ) )
  .retryWhen( obs -> obs
            .takeWhile( error -> !( error instanceof CancellationException ) ) // <-------
            .flatMap( error -> Observable.timer(/* args */) ) 
  )
  .subscribe( result -> useResult( result ),
              error -> handleError( error )
);

在这里,如果错误不是CancellationException类型,我们只会让错误通过(您可以将其替换为您的错误类型)。这将完成序列。

如果您希望序列以错误结束,我们需要更改flatMap逻辑:

 .retryWhen(obs -> obs
      .flatMap( error -> {
           if (error instanceof CancellationException) {
               return Observable.error(error);
           }
           return Observable.timer(/* args */);
      })
 )

请注意,在Observable.empty()中返回flatMap并不会结束序列,因为它只是表示要合并的源是空的,但可能还有其他内部源。特别是retryWhenempty()会无限期地挂起序列,因为不会有任何信号表示重试或序列结束。

修改

根据你的措辞,我认为getCancellationMessage()是一个热门的观察者。必须遵守热观察,以便接收他们的事件或错误。当retryWhen运算符由于timer()而处于其重试宽限期时,没有任何内容mergeWithgetCancellationMessage()订阅,因此无法停止那时的计时器。

您必须在执行timer时保留对它的订阅才能立即停止:

Observable<Object> cancel = getCancellationMessage();

Observable.defer( () -> getEndpoint() )
  .mergeWith( cancel )
  .flatMap( endpoint -> useEndpoint( endpoint ) )
  .retryWhen( obs -> obs
      .flatMap( error -> {
           if (error instanceof CancellationException) {
               return Observable.error(error);
           }
           return Observable.timer(/* args */).takeUntil( cancel );
      })
  )
  .subscribe( result -> useResult( result ),
              error -> handleError( error )
);

在这种情况下,如果cancel在计时器执行时触发,retryWhen将停止计时器并立即终止取消错误。

使用takeUntil是一个选项,正如您所发现的那样,mergeWith ( cancel )也可以正常运作。