合并一个Observables列表,直到所有完成为止


91

TL; DR 如何转换Task.whenAll(List<Task>)RxJava

我现有的代码使用Bolts构建了一系列异步任务,并等待所有这些任务完成后再执行其他步骤。本质上,按照Bolts站点上的示例,它会构建aList<Task>并返回一个列表,当列表中的所有任务Task完成时,将其标记为已完成。

我正在寻找替换方法BoltsRxJava并且我假设这种方法可以构建异步任务列表(大小未知)并将它们全部包装为一个Observable,但是我不知道如何做。

我试着看mergezipconcat等...但不能去工作的List<Observable>,我会被建立,因为他们似乎都面向工作的只有两个Observables,如果我理解正确的文档在一个时间。

我正在尝试学习RxJava,但仍然很陌生,因此,如果这是一个明显的问题或在文档中的某个地方进行了说明,请原谅我;我尝试搜寻。任何帮助将非常感激。

Answers:


73

听起来您正在寻找Zip运算符

有几种不同的用法,让我们来看一个例子。假设我们有一些不同类型的简单可观察物:

Observable<Integer> obs1 = Observable.just(1);
Observable<String> obs2 = Observable.just("Blah");
Observable<Boolean> obs3 = Observable.just(true);

等待他们全部的最简单方法是这样的:

Observable.zip(obs1, obs2, obs3, (Integer i, String s, Boolean b) -> i + " " + s + " " + b)
.subscribe(str -> System.out.println(str));

请注意,在zip函数中,参数的具体类型与要压缩的可观察对象的类型相对应。

也可以直接压缩可观察对象的列表:

List<Observable<?>> obsList = Arrays.asList(obs1, obs2, obs3);

Observable.zip(obsList, (i) -> i[0] + " " + i[1] + " " + i[2])
.subscribe(str -> System.out.println(str));

...或通过将列表包装到中Observable<Observable<?>>

Observable<Observable<?>> obsObs = Observable.from(obsList);

Observable.zip(obsObs, (i) -> i[0] + " " + i[1] + " " + i[2])
.subscribe(str -> System.out.println(str));

但是,在这两种情况下,zip函数只能接受一个Object[]参数,因为列表中的可观察对象的类型及其数量是未知的。这意味着zip函数必须检查参数的数量并进行相应的转换。

无论如何,以上所有示例最终都会打印出来 1 Blah true

编辑:使用Zip时,请确保Observables所有被压缩的拉链发出相同数量的项目。在以上示例中,所有三个可观察对象都发出了一个项目。如果我们要将它们更改为以下内容:

Observable<Integer> obs1 = Observable.from(new Integer[]{1,2,3}); //Emits three items
Observable<String> obs2 = Observable.from(new String[]{"Blah","Hello"}); //Emits two items
Observable<Boolean> obs3 = Observable.from(new Boolean[]{true,true}); //Emits two items

然后1, Blah, True2, Hello, True是传递到zip函数中的唯一项目。3由于其他可观察物均已完成,因此该项目将永远不会被压缩。


9
如果其中一个呼叫失败,这将无法正常工作。在这种情况下,所有通话都将丢失。
StarWind0 '16

1
@ StarWind0您可以通过使用跳过错误onErrorResumeNext,例如:Observable.zip(ob1, ob2........).onErrorResumeNext(Observable.<String>empty())
vuhung3990 '18

如果我有100个观测值怎么办?
Krzysztof Kubicki,

79

flatMap如果您具有动态任务组成,则可以使用。像这样:

public Observable<Boolean> whenAll(List<Observable<Boolean>> tasks) {
    return Observable.from(tasks)
            //execute in parallel
            .flatMap(task -> task.observeOn(Schedulers.computation()))
            //wait, until all task are executed
            //be aware, all your observable should emit onComplemete event
            //otherwise you will wait forever
            .toList()
            //could implement more intelligent logic. eg. check that everything is successful
            .map(results -> true);
}

并行执行的另一个很好的例子

注意:我真的不知道您对错误处理的要求。例如,如果只有一项任务失败该怎么办。我认为您应该验证这种情况。


16
考虑到问题指出“列表中的所有任务何时完成”,这应该是可接受的答案。zip一项任务完成后立即通知完成,因此不适用。
user3707125

1
@MyDogTom:可以使用Java7语法(不是lambda)版本更新答案吗?
sanedroid

3
@PoojaGaikwad使用lambda更具可读性。只需更换与第一拉姆达new Func1<Observable<Boolean>, Observable<Boolean>>()...和第二个与new Func1<List<Boolean>, Boolean>()
MyDogTom

@soshial RxJava 2是RxJava曾经发生的最糟糕的事情,是的
egorikem '19

15

在提出的建议中,zip()实际上彼此结合了可观察到的结果,可能是(也可能不是)想要的结果,但未在问题中提出。在这个问题中,所需要的只是每个操作的执行,要么是一对一或并行执行(未指定,但是链接的Bolts示例是关于并行执行的)。另外,当任何可观察对象完成时,zip()将立即完成,因此违反了要求。

对于Observables的并行执行,另一个答案中提供的flatMap()很好,但是merge()会更简单。请注意,合并将在任何一个Observable发生错误时退出,如果您宁愿将退出推迟到所有Observable完成之前,则应查看mergeDelayError()

对于一对一,我认为应使用Observable.concat()静态方法。其javadoc状态如下:

concat(java.lang.Iterable> sequence)将一个Oberable的Iterable展平为一个Observable,一个接一个,而不进行交织

如果您不希望并行执行,这听起来像您想要的。

另外,如果您只对完成任务感兴趣,而不对返回值感兴趣,则应该考虑使用Completable而不是Observable

TLDR:对于任务和oncompletion事件完成时的一对一执行,我认为Completable.concat()最合适。对于并行执行,Completable.merge()或Completable.mergeDelayError()听起来像解决方案。前者将在可完成的任何对象上出现任何错误时立即停止运行,后者将即使它们中的一个存在错误也将其全部执行,然后才报告错误。


2

您可能看了zip使用2个Observable的运算符。

还有静态方法 Observable.zip。它具有一种对您有用的表格:

zip(java.lang.Iterable<? extends Observable<?>> ws, FuncN<? extends R> zipFunction)

您可以查看更多的Javadoc。


2

与科特林

Observable.zip(obs1, obs2, BiFunction { t1 : Boolean, t2:Boolean ->

})

设置函数参数的类型很重要,否则会出现编译错误

最后一个参数类型随参数数量而变化:BiFunction for 2 Function3 for 3 Function4 for 4 ...


1

我正在用JavaRx Observables和RxKotlin在Kotlin中编写一些计算量代码。我想观察一个要完成的可观察对象的列表,同时给我提供进度和最新结果的更新。最后,它将返回最佳计算结果。一个额外的要求是使用我所有的cpu内核并行运行Observables。我最终得到了以下解决方案:

@Volatile var results: MutableList<CalculationResult> = mutableListOf()

fun doALotOfCalculations(listOfCalculations: List<Calculation>): Observable<Pair<String, CalculationResult>> {

    return Observable.create { subscriber ->
        Observable.concatEager(listOfCalculations.map { calculation: Calculation ->
            doCalculation(calculation).subscribeOn(Schedulers.computation()) // function doCalculation returns an Observable with only one result
        }).subscribeBy(
            onNext = {
                results.add(it)
                subscriber.onNext(Pair("A calculation is ready", it))

            },
            onComplete = {
                subscriber.onNext(Pair("Finished: ${results.size}", findBestCalculation(results)) 
                subscriber.onComplete()
            },
            onError = {
                subscriber.onError(it)
            }
        )
    }
}

不熟悉RxKotlin或@Volatile,但是如果同时被多个线程调用,将如何工作?结果会怎样?
eis

0

我遇到了类似的问题,我需要从rest调用中获取搜索项,同时还需要从最近的SearchProvider.AUTHORITY中集成保存的建议,并将它们组合到一个统一的列表中。我正在尝试使用@MyDogTom解决方案,很遗憾,RxJava中没有Observable.from。经过一番研究,我得到了一个对我有用的解决方案。

 fun getSearchedResultsSuggestions(context : Context, query : String) : Single<ArrayList<ArrayList<SearchItem>>>
{
    val fetchedItems = ArrayList<Observable<ArrayList<SearchItem>>>(0)
    fetchedItems.add(fetchSearchSuggestions(context,query).toObservable())
    fetchedItems.add(getSearchResults(query).toObservable())

    return Observable.fromArray(fetchedItems)
        .flatMapIterable { data->data }
        .flatMap {task -> task.observeOn(Schedulers.io())}
        .toList()
        .map { ArrayList(it) }
}

我从可观察对象数组中创建了一个可观察对象,其中包含根据查询来自互联网的建议和结果列表。之后,您只需使用flatMapIterable查看这些任务,然后使用flatmap运行它们,然后将结果放置在数组中,然后可以将其提取到回收视图中。


0

如果您使用Project Reactor,则可以使用Mono.when

Mono.when(publisher1, publisher2)
.map(i-> {
    System.out.println("everything is done!");
    return i;
}).block()
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.