web-dev-qa-db-fra.com

Combinez une liste d'observables et attendez que tout soit terminé

TL; DR Comment convertir Task.whenAll(List<Task>) en RxJava?

Mon code existant utilise Bolts pour créer une liste de tâches asynchrones et attend que toutes ces tâches se terminent avant d'exécuter d'autres étapes. Essentiellement, il construit un List<Task> et renvoie un seul Task qui est marqué comme terminé lorsque toutes les tâches de la liste sont terminées, comme indiqué dans le point - exemple sur le site Bolts .

Je cherche à remplacer Bolts par RxJava et je suppose que cette méthode permet de créer une liste de tâches asynchrones (taille inconnue à l'avance) et de les regrouper dans une seule Observable est possible, mais je ne sais pas comment.

J'ai essayé de regarder merge, Zip, concat etc ... mais je ne peux pas me mettre au travail sur le List<Observable> que je construirais en même temps ils semblent tous conçus pour ne travailler que sur deux Observables à la fois si je comprends bien la documentation.

J'essaie d'apprendre RxJava et je suis encore très novice, alors pardonnez-moi s'il s'agit d'une question évidente ou expliquée quelque part dans la documentation; J'ai essayé de chercher. Toute aide serait très appréciée.

73
Craig Russell

On dirait que vous cherchez le Opérateur Zip .

Il y a différentes façons de l'utiliser, alors prenons un exemple. Disons que nous avons quelques observables simples de différents types:

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

Le moyen le plus simple de les attendre tous ressemble à ceci:

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

Notez que dans la fonction Zip, les paramètres ont des types concrets qui correspondent aux types des observables compressés.

Zipper une liste d'observables est également possible, soit directement:

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

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

... ou en encapsulant la liste dans 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));

Cependant, dans ces deux cas, la fonction Zip ne peut accepter qu'un seul paramètre Object[] puisque les types d'observables de la liste ne sont pas connus à l'avance, ainsi que leur nombre. Cela signifie que la fonction Zip devrait vérifier le nombre de paramètres et les convertir en conséquence.

Quoi qu'il en soit, tous les exemples ci-dessus finiront par imprimer 1 Blah true

EDIT: Lorsque vous utilisez Zip, assurez-vous que Observables étant compressé, tous émettent le même nombre d'éléments. Dans les exemples ci-dessus, les trois observables ont émis un seul élément. Si nous devions les changer en quelque chose comme ceci:

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

Alors, 1, Blah, True et 2, Hello, True seront les seuls éléments passés dans la ou les fonctions Zip. L'élément 3 ne sera jamais compressé, les autres éléments observables étant terminés.

58
Malt

Vous pouvez utiliser flatMap si vous avez une composition dynamique de tâches. Quelque chose comme ça:

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

n autre bon exemple d'exécution parallèle

Remarque: je ne connais pas vraiment vos exigences en matière de traitement des erreurs. Par exemple, que faire si une seule tâche échoue? Je pense que vous devriez vérifier ce scénario.

70
MyDogTom

Parmi les suggestions proposées, Zip () combine en réalité des résultats observables les uns avec les autres, ce qui peut être ou ne pas être ce qui est souhaité, mais n'a pas été demandé dans la question. Dans la question, tout ce qui était souhaité était l'exécution de chacune des opérations, une par une ou en parallèle (ce qui n'était pas spécifié, mais l'exemple de Bolts lié concernait l'exécution en parallèle). En outre, Zip () se terminera immédiatement à la fin de l’une des variables observables, ce qui constitue donc une violation des exigences.

FlatMap () présenté dans l'autre réponse c'est bien, mais merge () serait plus simple. Notez que la fusion se terminera en cas d'erreur de l'un des observables. Si vous reportez plutôt la sortie jusqu'à ce que tous les observables soient terminés, vous devriez regarder mergeDelayError () .

Pour un par un, je pense que méthode statique Observable.concat () devrait être utilisé. Son javadoc déclare comme ceci:

concat (Java.lang.Iterable> sequence) Aplatit un itérable d'observables en un observable, l'un après l'autre, sans les entrelacer

ce qui ressemble à ce que vous recherchez si vous ne voulez pas une exécution parallèle.

De plus, si vous êtes seulement intéressé par l'achèvement de votre tâche, et ne renvoyez pas de valeurs, vous devriez probablement vous pencher sur Completable au lieu de Observable .

TLDR: pour l’exécution des tâches une par une et l’événement oncompletion une fois celles-ci terminées, je pense que Completable.concat () convient le mieux. Pour une exécution parallèle, Completable.merge () ou Completable.mergeDelayError () ressemble à la solution. Le premier s’arrêtera immédiatement en cas d’erreur sur tout ce qui peut être achevé, le dernier les exécutera tous, même si l’un d’eux a une erreur, et ne le signale qu’à ce moment-là.

9
eis

Avec kolin

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

})

Il est important de définir le type des arguments de la fonction, sinon vous aurez des erreurs de compilation.

Le dernier type d'argument change avec le nombre d'arguments: BiFunction for 2 Function3 for 3 Function4 for 4 ...

3
Kevin ABRIOUX

Vous avez probablement examiné l'opérateur Zip fonctionnant avec 2 observables.

Il y a aussi la méthode statique Observable.Zip. Il a un formulaire qui devrait vous être utile:

Zip(Java.lang.Iterable<? extends Observable<?>> ws, FuncN<? extends R> zipFunction)

Vous pouvez consulter le javadoc pour plus.

1
LordRaydenMK

J'écris du code de calcul de calcul dans Kotlin avec JavaRx Observables et RxKotlin. Je souhaite observer une liste d'observables à compléter et me donner entre-temps une mise à jour des progrès et des derniers résultats. À la fin, il renvoie le meilleur résultat de calcul. Une exigence supplémentaire consistait à exécuter Observables en parallèle pour utiliser tous mes cœurs de processeur. J'ai fini avec cette solution:

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