简体   繁体   中英

Spring Reactor Flux, how to subscribe and later block until all done

Spring reactor provides blocklast() if I want to be synchronous and block until all elements are done

But what if I want to continue a bit and then block until all elements are done?

( I do not want to perform a busy wait using isDisposed )

Do I need to do it myself with my own signal triggered by onComplete, or is there a better built in API?

//reactor provides blocklast if I want to be synchronous and block until all elements are done
//Integer data = Flux.range(1,10).delayElements(Duration.ofSeconds(1)).doOnNext((Integer next) -> System.out.println(next + " on thread " + Thread.currentThread().getName())).blockLast();
 
//but what if I want to continue a bit and then block
//do I need to do it myself like this? is there a better way?
Object signal = new Object(); 
Flux.range(1,10).delayElements(Duration.ofSeconds(1)).doOnNext((Integer next) -> System.out.println(next + " on thread " + Thread.currentThread().getName())).doOnComplete(()->{synchronized(signal) { signal.notify();}}).subscribe();
// do some other work here, then wait for done
synchronized (signal) {
    signal.wait();
}
System.out.println("All done");

There is sadly no built-in API for this purpose, but there are better ways of doing this. Your Object.wait will hang indefinitely if the Flux completes before you're done with your other work. You can solve this in multiple ways:

Monitor completion with a second Publisher

Here I create a second Publisher using Sinks.Empty , who's only purpose is to emit an onComplete signal when the original Flux completes. We place the Sinks.Empty.emitEmpty() trigger into the Flux.doOnComplete() hook, then later block using .asMono().block() . If you subscribe late, the Mono will complete immediately:

Sinks.Empty<Void> completionSink = Sinks.empty();

Flux.range(1,10)
        .delayElements(Duration.ofSeconds(1))
        .doOnNext((Integer next) -> System.out.println(next + " on thread " + Thread.currentThread().getName()))
        .doOnComplete(() -> completionSink.emitEmpty(Sinks.EmitFailureHandler.FAIL_FAST))
        .subscribe();

// do some other work here, then wait for done
completionSink.asMono().block();
System.out.println("All done");

Put your other work in another Publisher

Alternatively, you can put your other work in a second Publisher using Mono.fromRunnable , then use Flux.merge to subscribe to both Publishers simultaneously. Flux.merge will emit all items from both Publishers and completes only if both are completed. Since your Mono.fromRunnable does not emit any items, the resulting Flux will only contain the items of the original Flux.

Flux<Integer> flux = Flux.range(1, 10)
        .delayElements(Duration.ofSeconds(1))
        .doOnNext((Integer next) -> System.out.println(next + " on thread " + Thread.currentThread().getName()));
Mono<Void> otherWork = Mono.fromRunnable(() -> {
    // do some other work here
});
Flux.merge(flux, otherWork).blockLast();
System.out.println("All done");

Note that Flux.merge puts both Publishers onto the same Scheduler, which will cause problems if otherWork is not asynchronous or if the original Flux never completes. You can solve this by assigning different Schedulers to each Publisher.

I would simplify the answer of @Patrick Hooijer and use CountDownLatch , this is perfect element of concurrent library, that waits while its value is changed to zero.

You just call.await() and its just awaits while the Flux call countDown in doOnComplete method.

    CountDownLatch countDownLatch = new CountDownLatch(1);

    Flux.range(1,10)
            .delayElements(Duration.ofSeconds(1))
            .doOnNext((Integer next) -> System.out.println(next + " on thread " + Thread.currentThread().getName()))
            .doOnComplete(countDownLatch::countDown)
            .subscribe();

    countDownLatch.await();

    System.out.println("All done");

There is sadly no built-in API for this purpose

This essential behaviour for me, thus we won't have to wait for any results if the main thread is getting to close, thus Flux could be a lot more time consuming than just throwing numbers, we would probably cannot close the main thread in this case.

But it depends on creators vision of that library, of course.

As I can see in your question, you want some other api to replace blockLast, right? And as far as I know, you have to implement this yourself. There is no built-in api for this purpose.

Mono.zip(
                        Mono.fromCallable(() -> {
                            //something
                            System.out.println("Completed doing");
                            return 1;
                        }).delaySubscription(Duration.ofSeconds(12)).subscribeOn(Schedulers.boundedElastic()),
                        Flux.range(1, 10)
                                .delayElements(Duration.ofSeconds(1))
                                .doOnNext((Integer next) -> System.out.println(next + " on thread " + Thread.currentThread().getName()))
                                .last()
                )
                .subscribe(__ -> System.out.println("All Done"));

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM