Combina un elenco di osservabili e attendi che tutto sia completato


91

TL; DR Come convertire Task.whenAll(List<Task>)in RxJava?

Il mio codice esistente utilizza Bolts per creare un elenco di attività asincrone e attende che tutte queste attività finiscano prima di eseguire altri passaggi. In sostanza, crea un List<Task>e restituisce un singolo Taskche è contrassegnato come completato quando tutte le attività nell'elenco sono state completate, come nell'esempio sul sito Bolts .

Sto cercando di sostituire Boltscon RxJavae presumo che questo metodo per creare un elenco di attività asincrone (dimensioni non note in anticipo) e avvolgerle tutte in una Observablesia possibile, ma non so come.

Ho provato a guardare merge, zip, concatecc ... ma non riesco a lavorare sul List<Observable>che sarei edificazione come tutti sembrano orientati a lavorare su solo due Observablesalla volta, se ho capito correttamente la documentazione.

Sto cercando di imparare RxJavae sono ancora molto nuovo, quindi perdonami se questa è una domanda ovvia o spiegata da qualche parte nei documenti; Ho provato a cercare. Qualsiasi aiuto sarebbe molto apprezzato.

Risposte:


73

Sembra che tu stia cercando l' operatore Zip .

Esistono diversi modi per utilizzarlo, quindi diamo un'occhiata a un esempio. Supponiamo di avere alcune semplici osservabili di diverso tipo:

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

Il modo più semplice per aspettarli tutti è qualcosa del genere:

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

Si noti che nella funzione zip, i parametri hanno tipi concreti che corrispondono ai tipi di osservabili compressi.

È anche possibile comprimere un elenco di osservabili, direttamente:

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

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

... o avvolgendo l'elenco in un Observable<Observable<?>>:

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

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

Tuttavia, in entrambi i casi, la funzione zip può accettare un solo Object[]parametro poiché i tipi di osservabili nell'elenco non sono noti a priori così come il loro numero. Ciò significa che la funzione zip dovrebbe controllare il numero di parametri e lanciarli di conseguenza.

Indipendentemente da ciò, alla fine verranno stampati tutti gli esempi precedenti 1 Blah true

MODIFICA: Quando si utilizza Zip, assicurarsi che Observablestutti i file zippati emettano lo stesso numero di elementi. Negli esempi precedenti tutti e tre gli osservabili hanno emesso un singolo elemento. Se dovessimo cambiarli in qualcosa di simile:

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

Quindi 1, Blah, Truee 2, Hello, Truesarebbero gli unici elementi passati nelle funzioni zip. L'oggetto 3non verrebbe mai zippato poiché gli altri osservabili sono stati completati.


9
Questo non funzionerà se una delle chiamate fallisce. In tal caso tutte le chiamate andranno perse.
StarWind0

1
@ StarWind0 puoi saltare l'errore usando onErrorResumeNext, ad esempio:Observable.zip(ob1, ob2........).onErrorResumeNext(Observable.<String>empty())
vuhung3990

E se avessi 100 osservabili?
Krzysztof Kubicki

80

È possibile utilizzare flatMapnel caso in cui si disponga di una composizione dinamica delle attività. Qualcosa come questo:

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

Un altro buon esempio di esecuzione parallela

Nota: non conosco realmente i tuoi requisiti per la gestione degli errori. Ad esempio cosa fare se solo un'attività fallisce. Penso che dovresti verificare questo scenario.


17
Questa dovrebbe essere la risposta accettata considerando che la domanda afferma "quando tutte le attività nell'elenco sono state completate". zipnotifica il completamento non appena una delle attività è stata completata e quindi non è applicabile.
user3707125

1
@MyDogTom: puoi aggiornare la risposta con la versione della sintassi Java7 (non lambda)?
sanedroid

3
@PoojaGaikwad Con lambda è più leggibile. Basta sostituire il primo lambda con new Func1<Observable<Boolean>, Observable<Boolean>>()...e il secondo connew Func1<List<Boolean>, Boolean>()
MyDogTom

@soshial RxJava 2 è la cosa peggiore che sia mai successa con RxJava, sì
egorikem

16

Dei suggerimenti proposti, zip () combina effettivamente risultati osservabili tra loro, che può essere o meno ciò che si desidera, ma non è stato chiesto nella domanda. Nella domanda, tutto ciò che si desiderava era l'esecuzione di ciascuna delle operazioni, una per una o in parallelo (cosa non specificata, ma l'esempio di Bolt collegato riguardava l'esecuzione parallela). Inoltre, zip () verrà completato immediatamente al completamento di una qualsiasi delle osservabili, quindi è in violazione dei requisiti.

Per l'esecuzione parallela di Observables, flatMap () presentato nell'altra risposta va bene, ma merge () sarebbe più semplice. Nota che l'unione uscirà in caso di errore di uno qualsiasi degli osservabili, se preferisci posticipare l'uscita fino al termine di tutte le osservabili, dovresti guardare mergeDelayError () .

Per uno per uno, penso che dovrebbe essere utilizzato il metodo statico Observable.concat () . Il suo javadoc afferma in questo modo:

concat (java.lang.Iterable> sequences) Appiattisce un Iterable di Observables in un Observable, uno dopo l'altro, senza intercalarli

che suona come quello che stai cercando se non vuoi l'esecuzione parallela.

Inoltre, se sei interessato solo al completamento della tua attività, non a restituire valori, dovresti probabilmente esaminare Completable invece di Observable .

TLDR: per l'esecuzione una alla volta delle attività e l'evento al completamento quando vengono completate, penso che Completable.concat () sia il più adatto. Per l'esecuzione parallela, Completable.merge () o Completable.mergeDelayError () suona come la soluzione. Il primo si fermerà immediatamente ad ogni errore su qualsiasi completabile, il secondo li eseguirà tutti anche se uno di essi ha un errore, e solo allora lo segnala.


2

Probabilmente hai guardato l' zipoperatore che funziona con 2 osservabili.

C'è anche il metodo statico Observable.zip. Ha una forma che dovrebbe esserti utile:

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

Puoi controllare il javadoc per ulteriori informazioni.


2

Con Kotlin

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

})

È importante impostare il tipo per gli argomenti della funzione o si verificheranno errori di compilazione

L'ultimo tipo di argomento cambia con il numero di argomenti: BiFunction per 2 Funzione3 per 3 Funzione4 per 4 ...


1

Sto scrivendo del codice di calcolo per il sollevamento in Kotlin con JavaRx Observables e RxKotlin. Voglio osservare un elenco di osservabili da completare e nel frattempo darmi un aggiornamento con lo stato di avanzamento e l'ultimo risultato. Alla fine restituisce il miglior risultato di calcolo. Un requisito aggiuntivo era quello di eseguire Observables in parallelo per l'utilizzo di tutti i core della mia cpu. Ho finito con questa soluzione:

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

non hai familiarità con RxKotlin o @Volatile, ma come funzionerebbe se questo viene chiamato da più thread contemporaneamente? Cosa succederebbe ai risultati?
eis

0

Ho avuto un problema simile, avevo bisogno di recuperare gli elementi di ricerca dalla chiamata di riposo e allo stesso tempo integrare i suggerimenti salvati da un RecentSearchProvider.AUTHORITY e combinarli insieme in un elenco unificato. Stavo cercando di utilizzare la soluzione @MyDogTom, sfortunatamente non esiste Observable.from in RxJava. Dopo alcune ricerche ho trovato una soluzione che ha funzionato per me.

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

Ho creato un osservabile dalla serie di osservabili che contiene elenchi di suggerimenti e risultati da Internet a seconda della query. Dopodiché, devi semplicemente esaminare quelle attività con flatMapIterable ed eseguirle utilizzando flatmap, posizionare i risultati in un array, che può essere successivamente recuperato in una visualizzazione di riciclo.


0

Se usi Project Reactor, puoi usare Mono.when.

Mono.when(publisher1, publisher2)
.map(i-> {
    System.out.println("everything is done!");
    return i;
}).block()
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.