รวมรายชื่อ Observables และรอจนกว่าทั้งหมดจะเสร็จสมบูรณ์


91

TL; DR วิธีการแปลงTask.whenAll(List<Task>)เป็นRxJava?

รหัสที่มีอยู่ของฉันใช้ Bolts เพื่อสร้างรายการงานแบบอะซิงโครนัสและรอจนกว่างานเหล่านั้นทั้งหมดจะเสร็จสิ้นก่อนที่จะดำเนินการขั้นตอนอื่น ๆ โดยพื้นฐานแล้วมันสร้างขึ้นList<Task>และส่งกลับคนเดียวTaskที่ถูกทำเครื่องหมายเป็นเสร็จสมบูรณ์เมื่อทุกงานในรายการเสร็จสมบูรณ์ตามตัวอย่างเช่นในเว็บไซต์ของสกรู

ฉันต้องการแทนที่Boltsด้วยRxJavaและฉันสมมติว่าวิธีนี้ในการสร้างรายการงาน async (ขนาดที่ไม่ทราบล่วงหน้า) และรวมทั้งหมดเป็นงานเดียว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 รายการ?
Krzysztof Kubicki

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 Syntax (ไม่ใช่แลมบ์ดา) ได้หรือไม่?
sanedroid

3
@PoojaGaikwad ด้วยแลมด้ามันน่าอ่านมากขึ้น เพียงแค่แทนที่ lambda ตัวแรกnew Func1<Observable<Boolean>, Observable<Boolean>>()...และตัวที่สองด้วยnew Func1<List<Boolean>, Boolean>()
MyDogTom

@soshial RxJava 2 เป็นสิ่งที่เลวร้ายที่สุดที่เคยเกิดขึ้นกับ RxJava ใช่แล้ว
egorikem

16

จากข้อเสนอแนะที่เสนอzip ()ได้รวมผลลัพธ์ที่สังเกตได้เข้าด้วยกันซึ่งอาจเป็นสิ่งที่ต้องการหรือไม่ก็ได้ แต่ไม่ได้ถูกถามในคำถาม ในคำถามสิ่งที่ต้องการคือการดำเนินการของแต่ละการดำเนินการไม่ว่าจะทีละรายการหรือแบบขนาน (ซึ่งไม่ได้ระบุไว้ แต่ตัวอย่างสลักเกลียวที่เชื่อมโยงนั้นเกี่ยวกับการดำเนินการแบบขนาน) นอกจากนี้ zip () จะเสร็จสมบูรณ์ทันทีเมื่อสิ่งที่สังเกตเห็นเสร็จสมบูรณ์ดังนั้นจึงเป็นการละเมิดข้อกำหนด

สำหรับการดำเนินการแบบขนานของ Observables flatMap () ที่นำเสนอในคำตอบอื่นนั้นใช้ได้ แต่merge ()จะตรงไปตรงมามากกว่า โปรดทราบว่าการผสานจะออกจากข้อผิดพลาดของ Observables ใด ๆ หากคุณต้องการเลื่อนการออกไปจนกว่าการสังเกตการณ์ทั้งหมดจะเสร็จสิ้นคุณควรดูที่mergeDelayError ()()

สำหรับทีละรายการฉันคิดว่าควรใช้วิธีการแบบคงที่ Observable.concat () javadoc ระบุดังนี้:

concat (java.lang.Iterable> sequences) แผ่สิ่งที่สามารถทำซ้ำได้ของ Observables ให้เป็น Observable หนึ่งอันต่อกัน

ซึ่งดูเหมือนสิ่งที่คุณต้องการหากคุณไม่ต้องการการดำเนินการแบบขนาน

นอกจากนี้หากคุณสนใจเพียงแค่การทำงานของคุณให้เสร็จไม่ใช่คืนค่าคุณควรมองหาCompletableแทนObservableสังเกต

TLDR: สำหรับการดำเนินการทีละงานและเหตุการณ์ oncompletion เมื่อเสร็จสมบูรณ์ฉันคิดว่า Completable.concat () เหมาะสมที่สุด สำหรับการดำเนินการแบบขนาน Completable.merge () หรือ Completable.mergeDelayError () ดูเหมือนเป็นโซลูชัน ข้อผิดพลาดก่อนหน้านี้จะหยุดทันทีเมื่อเกิดข้อผิดพลาดใด ๆ ในการดำเนินการใด ๆ ที่สมบูรณ์ข้อผิดพลาดหลังจะดำเนินการทั้งหมดแม้ว่าข้อผิดพลาดข้อใดข้อหนึ่งจะมีข้อผิดพลาดก็ตาม


2

คุณอาจดูที่ไฟล์ zipดำเนินการที่ทำงานร่วมกับ Observables 2 ตัว

Observable.zipนอกจากนี้ยังมีวิธีการคง มีรูปแบบหนึ่งที่น่าจะเป็นประโยชน์สำหรับคุณ:

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

คุณสามารถตรวจสอบjavadoc สำหรับข้อมูลเพิ่มเติม


2

กับ Kotlin

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

})

สิ่งสำคัญคือต้องตั้งค่าประเภทสำหรับอาร์กิวเมนต์ของฟังก์ชันมิฉะนั้นคุณจะมีข้อผิดพลาดในการคอมไพล์

ประเภทอาร์กิวเมนต์สุดท้ายเปลี่ยนด้วยจำนวนอาร์กิวเมนต์: BiFunction for 2 Function3 for 3 Function4 for 4 ...


1

ฉันกำลังเขียนโค้ดยกระดับการคำนวณใน Kotlin ด้วย JavaRx Observables และ RxKotlin ฉันต้องการสังเกตรายการสิ่งที่สังเกตได้เพื่อให้เสร็จสมบูรณ์และในระหว่างนี้จะแจ้งความคืบหน้าและผลลัพธ์ล่าสุดให้ฉันทราบ ในตอนท้ายจะส่งกลับผลการคำนวณที่ดีที่สุด ข้อกำหนดเพิ่มเติมคือการเรียกใช้ Observables แบบขนานสำหรับการใช้แกน cpu ทั้งหมดของฉัน ฉันลงเอยด้วยวิธีนี้:

@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 แต่น่าเสียดายที่ไม่มี Observable.from ใน RxJava หลังจากการวิจัยบางอย่างฉันได้วิธีแก้ปัญหาที่ใช้ได้ผล

 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 และเรียกใช้งานโดยใช้แฟลตแมปวางผลลัพธ์ในอาร์เรย์ซึ่งสามารถดึงข้อมูลไปยังมุมมองรีไซเคิลได้ในภายหลัง


0

หากคุณใช้ Project Reactor คุณสามารถใช้Mono.when.

Mono.when(publisher1, publisher2)
.map(i-> {
    System.out.println("everything is done!");
    return i;
}).block()
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.