Об’єднайте список спостережуваних і почекайте, поки все завершиться


91

TL; DR Як конвертувати Task.whenAll(List<Task>)в RxJava?

Мій існуючий код використовує Bolts для складання списку асинхронних завдань і чекає, поки всі ці завдання закінчаться, перш ніж виконувати інші дії. По суті, він створює a List<Task>і повертає сингл, Taskякий позначається як виконаний, коли всі завдання у списку завершені, як у прикладі на сайті Bolts .

Я прагну замінити Boltsна, RxJavaі я припускаю, що цей спосіб складання списку асинхронних завдань (розмір невідомий заздалегідь) і обгортання їх усіх в єдине ціле Observableможливо, але я не знаю як.

Я намагався дивитися на merge, zip, і concatт.д ... але не може приступити до роботи на List<Observable>що я буду будувати вгору , як і всі вони , здається , спрямовані працювати тільки на двох , Observablesв той час , коли я розумію правильно документи.

Я намагаюся вчитися, RxJavaі все ще дуже нова в цьому, тому пробачте мене, якщо це очевидне запитання або десь пояснено в документації; Я спробував шукати. Будь-яка допомога буде дуже вдячна.

Відповіді:


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, Trueі 2, Hello, Trueбули б єдиними елементами, переданими у функцію zip. Елемент 3ніколи не буде застібатися на блискавку, оскільки інші спостережувані дані завершено.


9
Це не спрацює, якщо один із дзвінків не вдається. У цьому випадку всі дзвінки будуть втрачені.
StarWind0

1
@ StarWind0 ви можете пропустити помилку, використовуючи onErrorResumeNextприклад:Observable.zip(ob1, ob2........).onErrorResumeNext(Observable.<String>empty())
vuhung3990

Що робити, якщо у мене є 100 спостережуваних?
Кшиштоф

80

Ви можете використовувати, 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);
}

Ще один хороший приклад паралельного виконання

Примітка: Я насправді не знаю ваших вимог щодо обробки помилок. Наприклад, що робити, якщо не вдається виконати лише одне завдання. Я думаю, вам слід перевірити цей сценарій.


17
Це має бути прийнятою відповіддю, враховуючи, що в питанні зазначається, "коли всі завдання у списку виконані". zipповідомляє про виконання, як тільки одне із завдань виконано і, отже, не застосовується.
user3707125

1
@MyDogTom: Чи можете ви оновити відповідь за допомогою версії синтаксису Java7 (не лямбда)?
sanedroid

3
@PoojaGaikwad З лямбдою це легше читати. Просто замініть першу new Func1<Observable<Boolean>, Observable<Boolean>>()...new Func1<List<Boolean>, Boolean>()
лямбду на,

@soshial RxJava 2 - це найгірше, що коли-небудь траплялося з RxJava, так
egorikem

15

З запропонованих пропозицій zip () насправді поєднує видимі результати між собою, які можуть бути чи не бути тим, що хочеться, але не задавались у питанні. У питанні, все, що хотілося, - це виконання кожної з операцій, або поодинці, або паралельно (що не було вказано, але зв’язаний приклад Болтів стосувався паралельного виконання). Крім того, zip () завершиться негайно, коли завершиться будь-яке з спостережуваних, тож це порушує вимоги.

Для паралельного виконання Observables, flatMap (), представлений в іншій відповіді , добре, але merge () буде більш прямим. Зверніть увагу, що злиття вийде з помилки будь-якого з спостережуваних. Якщо ви радше відкладете вихід, поки всі спостережувані не закінчаться, ви повинні шукати mergeDelayError () .

Я вважаю, що для поодинці слід використовувати статичний метод Observable.concat () . Його javadoc стверджує так:

concat (java.lang.Iterable> послідовності) Згладжує ітерабел спостережуваних в одне спостережуване, одне за іншим, не чергуючи їх

що звучить як те, за чим ви переслідуєте, якщо не хочете паралельного виконання.

Крім того, якщо ви зацікавлені лише у виконанні вашого завдання, а не у поверненні значень, вам, мабуть, слід дивитись на Completable замість Observable .

TLDR: для поодинокого виконання завдань і події завершення, коли вони завершені, я думаю, що Completable.concat () найкраще підходить. Для паралельного виконання Completable.merge () або Completable.mergeDelayError () звучить як рішення. Перший негайно зупиниться на будь-якій помилці будь-якого виконуваного файлу, другий виконає їх усі, навіть якщо один із них має помилку, і лише потім повідомляє про помилку.


2

Ви, мабуть, розглядали zipоператор, який працює з 2 Observables.

Існує також статичний метод 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 для 2 Функція3 для 3 Функція4 для 4 ...


1

Я пишу якийсь код обчислювального підняття в Kotlin за допомогою JavaRx Observables і RxKotlin. Я хочу переглянути список спостережуваних, який слід заповнити, а тим часом повідомити мені про хід і останні результати. В кінці він повертає найкращий результат обчислення. Додатковою вимогою було паралельне запуск 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

У мене була подібна проблема, мені потрібно було отримати елементи пошуку з виклику відпочинку, а також інтегрувати збережені пропозиції з RecentSearchProvider.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()
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.