Kombinieren Sie eine Liste von Observables und warten Sie, bis alle abgeschlossen sind

85

TL; DR Wie konvertiere ich Task.whenAll(List<Task>)in RxJava?

Mein vorhandener Code verwendet Bolts, um eine Liste asynchroner Aufgaben zu erstellen, und wartet, bis alle diese Aufgaben abgeschlossen sind, bevor andere Schritte ausgeführt werden. Im Wesentlichen wird eine erstellt List<Task>und eine einzelne zurückgegeben, Taskdie als erledigt markiert wird, wenn alle Aufgaben in der Liste abgeschlossen sind, wie im Beispiel auf der Bolts-Site .

Ich bin auf der Suche ersetzen Boltsmit RxJavaund ich gehe davon aus, diese Methode der eine Liste von Asynchron - Aufgaben Aufbau (Größe im Voraus nicht bekannt) und Wickeln sie alle in eine einzige Observableist möglich, aber ich weiß nicht, wie.

Ich habe versucht , Blick auf merge, zip, concatetc ... aber kann nicht zur Arbeit auf das bekommen , List<Observable>dass ich den Aufbau würde , da sie alle nur zwei zu arbeiten darauf ausgerichtet zu sein scheinen Observableszu einer Zeit , wenn ich die Dokumentation richtig verstehen.

Ich versuche zu lernen RxJavaund bin noch sehr neu darin. Verzeihen Sie mir, wenn dies eine offensichtliche Frage ist oder irgendwo in den Dokumenten erklärt wird. Ich habe versucht zu suchen. Jede Hilfe wäre sehr dankbar.

Craig Russell
quelle

Antworten:

69

Es hört sich so an, als würden Sie nach dem Zip-Operator suchen .

Es gibt verschiedene Verwendungsmöglichkeiten. Schauen wir uns also ein Beispiel an. Nehmen wir an, wir haben ein paar einfache Observablen verschiedener Typen:

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

Der einfachste Weg, auf sie alle zu warten, ist ungefähr so:

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

Beachten Sie, dass die Parameter in der Zip-Funktion konkrete Typen haben, die den Typen der zu komprimierenden Observablen entsprechen.

Das Zippen einer Liste von Observablen ist auch direkt möglich:

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

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

... oder indem Sie die Liste in ein Observable<Observable<?>>:

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

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

In beiden Fällen kann die Zip-Funktion jedoch nur einen einzigen Object[]Parameter akzeptieren , da die Typen der Observablen in der Liste sowie deren Anzahl nicht im Voraus bekannt sind. Dies bedeutet, dass die Zip-Funktion die Anzahl der Parameter überprüfen und entsprechend umwandeln müsste.

Unabhängig davon werden alle oben genannten Beispiele schließlich gedruckt 1 Blah true

BEARBEITEN: Wenn Sie Zip verwenden, stellen Sie sicher, Observablesdass alle , die gezippt werden, die gleiche Anzahl von Elementen ausgeben. In den obigen Beispielen emittierten alle drei Observablen einen einzelnen Gegenstand. Wenn wir sie in so etwas ändern würden:

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

Dann 1, Blah, Trueund 2, Hello, Truewäre das einzige Element, das an die Zip-Funktion (en) übergeben wird. Der Artikel 3würde niemals gezippt werden, da die anderen Observablen abgeschlossen sind.

Malz
quelle
9
Dies funktioniert nicht, wenn einer der Anrufe fehlschlägt. In diesem Fall gehen alle Anrufe verloren.
StarWind0
1
@ StarWind0 können Sie Fehler überspringen, indem Sie verwenden onErrorResumeNext, Beispiel:Observable.zip(ob1, ob2........).onErrorResumeNext(Observable.<String>empty())
vuhung3990
Was ist, wenn ich 100 Observable habe?
Krzysztof Kubicki
78

Sie können verwenden, flatMapwenn Sie dynamische Aufgaben Komposition haben. Etwas wie das:

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

Ein weiteres gutes Beispiel für die parallele Ausführung

Hinweis: Ich kenne Ihre Anforderungen an die Fehlerbehandlung nicht wirklich. Zum Beispiel, was zu tun ist, wenn nur eine Aufgabe fehlschlägt. Ich denke, Sie sollten dieses Szenario überprüfen.

MyDogTom
quelle
16
Dies sollte die akzeptierte Antwort sein, wenn man bedenkt, dass die Frage lautet "wenn alle Aufgaben in der Liste abgeschlossen sind". zipbenachrichtigt über den Abschluss, sobald eine der Aufgaben erledigt ist und ist daher nicht anwendbar.
user3707125
1
@MyDogTom: Können Sie die Antwort mit der Java7-Syntaxversion (nicht der Lambda-Version) aktualisieren?
Sanedroid
3
@PoojaGaikwad Mit Lambda ist es besser lesbar. Ersetzen Sie einfach das erste Lambda durch new Func1<Observable<Boolean>, Observable<Boolean>>()...und das zweite durchnew Func1<List<Boolean>, Boolean>()
MyDogTom
@soshial RxJava 2 ist das Schlimmste, was jemals mit RxJava passiert ist, ja
egorikem
13

Von den vorgeschlagenen Vorschlägen kombiniert zip () tatsächlich beobachtbare Ergebnisse miteinander, was möglicherweise das ist, was gewünscht wird oder nicht, aber in der Frage nicht gestellt wurde. In der Frage war alles, was gewünscht wurde, die Ausführung jeder der Operationen, entweder einzeln oder parallel (was nicht spezifiziert wurde, aber das Beispiel für verknüpfte Schrauben betraf die parallele Ausführung). Außerdem wird zip () sofort abgeschlossen, wenn eine der Observablen abgeschlossen ist, sodass die Anforderungen verletzt werden.

Für die parallele Ausführung von Observables ist flatMap () in der anderen Antwort in Ordnung, merge () wäre jedoch einfacher. Beachten Sie, dass die Zusammenführung bei einem Fehler eines der Observables beendet wird. Wenn Sie den Exit lieber verschieben, bis alle Observables abgeschlossen sind, sollten Sie sich mergeDelayError () ansehen .

Ich denke, für eins nach dem anderen sollte die statische Methode Observable.concat () verwendet werden. Seine Javadoc-Zustände lauten wie folgt:

concat (java.lang.Iterable> Sequenzen) Fasst eine Iterable von Observables nacheinander zu einer Observable zusammen, ohne sie zu verschachteln

Das klingt nach dem, wonach Sie suchen, wenn Sie keine parallele Ausführung wünschen.

Wenn Sie nur an der Fertigstellung Ihrer Aufgabe interessiert sind und keine Werte zurückgeben möchten, sollten Sie sich wahrscheinlich mit Completable anstelle von Observable befassen .

TLDR: Ich denke, Completable.concat () ist am besten geeignet, um Aufgaben und Ereignisse nach Abschluss einzeln auszuführen. Bei paralleler Ausführung klingt Completable.merge () oder Completable.mergeDelayError () nach der Lösung. Ersteres stoppt sofort bei einem Fehler bei einem Abschluss, letzteres führt alle aus, auch wenn einer von ihnen einen Fehler aufweist, und meldet den Fehler erst dann.

eis
quelle
2

Mit Kotlin

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

})

Es ist wichtig, den Typ für die Argumente der Funktion festzulegen, da sonst Kompilierungsfehler auftreten

Der letzte Argumenttyp ändert sich mit der Anzahl der Argumente: BiFunction for 2 Function3 for 3 Function4 for 4 ...

Kevin ABRIOUX
quelle
1

Sie haben sich wahrscheinlich den zipOperator angesehen, der mit 2 Observables arbeitet.

Es gibt auch die statische Methode Observable.zip. Es hat eine Form, die für Sie nützlich sein sollte:

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

Sie können das Javadoc für mehr überprüfen .

LordRaydenMK
quelle
1

Ich schreibe einen Rechencode in Kotlin mit JavaRx Observables und RxKotlin. Ich möchte eine Liste der zu vervollständigenden Observablen beobachten und mir in der Zwischenzeit ein Update mit dem Fortschritt und dem neuesten Ergebnis geben. Am Ende wird das beste Berechnungsergebnis zurückgegeben. Eine zusätzliche Anforderung bestand darin, Observables parallel auszuführen, um alle meine CPU-Kerne zu verwenden. Am Ende hatte ich diese Lösung:

@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)
            }
        )
    }
}
Sjors
quelle
nicht vertraut mit RxKotlin oder @Volatile, aber wie würde dies funktionieren, wenn dies von mehreren Threads gleichzeitig aufgerufen wird? Was würde mit den Ergebnissen passieren?
Eis
0

Ich hatte ein ähnliches Problem. Ich musste Suchelemente aus dem Restanruf abrufen und gleichzeitig gespeicherte Vorschläge aus einem RecentSearchProvider.AUTHORITY integrieren und zu einer einheitlichen Liste zusammenfassen. Ich habe versucht, die @ MyDogTom-Lösung zu verwenden, leider gibt es in RxJava keine Observable.from. Nach einigen Recherchen bekam ich eine Lösung, die für mich funktionierte.

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

Ich habe aus dem Array von Observablen ein Observable erstellt, das je nach Abfrage Listen mit Vorschlägen und Ergebnissen aus dem Internet enthält. Danach gehen Sie diese Aufgaben einfach mit flatMapIterable durch und führen sie mit flatmap aus. Platzieren Sie die Ergebnisse in einem Array, das später in eine Recyclingansicht abgerufen werden kann.

Anton Makov
quelle
0

Wenn Sie Project Reactor verwenden, können Sie verwenden Mono.when.

Mono.when(publisher1, publisher2)
.map(i-> {
    System.out.println("everything is done!");
    return i;
}).block()
Rogen
quelle