簡體   English   中英

如何阻塞直到下一個數據從熱通量發出?

[英]How to block until next data is emitted from hot Flux?

我有一些 function 返回一些Flux<Integer> 這個通量很熱,它正在發射實時數據。 執行一段時間后,我想阻塞直到發出下一個 Integer,然后分配給一個變量。 這個Integer可能不是第一個,也不會是最后一個。

我考慮blockFirst() ,但這會無限期地阻塞,因為 Flux 已經發出了 Integer。我不在乎 Integer 是 Flux 中的第一個還是最后一個,我只想阻塞直到發出下一個 Integer 並分配它到一個變量。

我該怎么做? 我想我可以訂閱 Flux,在第一個值之后取消訂閱,但我希望有更好的方法來做到這一點。

這取決於您的熱通量的重播/緩沖行為。 blockFirst()next()運算符都做同樣的事情:它們等待當前訂閱中收到的第一個值。

理解這一點非常重要,因為在熱通量的情況下,訂閱獨立於源數據發射。 第一個值不一定是上游發出的第一個值。 這是您當前訂閱者收到的第一個值,這取決於上游流行為。
在熱通量的情況下,它們如何將值傳遞給訂閱者取決於它們的緩沖和廣播策略。 對於這個答案,我將只關注緩沖方面:

  • 如果您的熱通量緩沖任何發射值(例如: Flux.share()Sinks.multicast().directBestEffort() ),那么blockFirst()next().block()運算符都滿足您的要求:等到下一個以阻塞方式發出實時數據。
    注意:如果用緩存和訂閱替換塊, next()的優點是允許變為非阻塞
  • 如果您的上游流量確實緩沖了一些過去的值,那么您的訂閱者/下游流量將不僅接收實時 stream。在它之前,它將接收(部分)上游歷史記錄。 在這種情況下,您將不得不使用更高級的策略來跳過值直到您想要的值。
    根據你的問題,我會說跳過值直到經過時間可以使用skipUntilOther(Mono.delay(wantedDuration))來完成。
    但是要小心,因為延遲是從你的訂閱開始的,而不是從上游訂閱開始的(為此,你需要上游提供定時元素,並切換到另一種策略)。
  • 了解 Reactor 禁止從某些線程(非彈性調度程序使用的線程)調用block()也很重要。

讓我們用代碼來演示所有這些。 在下面的程序中,有 4 個示例:

  1. 直接在非緩沖熱通量上使用next/blockFirst
  2. 緩沖熱通量上使用skipUntilOther
  3. 顯示阻塞有時會失敗
  4. 盡量避免塊操作

為清楚起見,對所有示例進行了注釋,並在主 function 中按順序啟動:

import java.time.Duration;
import java.util.concurrent.CountDownLatch;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

public class HotFlux {

    public static void main(String[] args) throws Exception {
        System.out.println("== 1. Hot flux without any buffering");
        noBuffer();

        System.out.println("== 2. Hot flux with buffering");
        withBuffer();

        // REMINDER: block operator is not always accepted by Reactor
        System.out.println("== 3. block called from a wrong context");
        blockFailsOnSomeSchedulers();

        System.out.println("== 4. Next value without blocking");
        avoidBlocking();
    }

    static void noBuffer() {
        // Prepare a hot flux thanks to share().
        var hotFlux = Flux.interval(Duration.ofMillis(100))
                          .share();

        // Prepare an operator that fetch the next value from live stream after a delay.
        var nextValue = Mono.delay(Duration.ofMillis(300))
                        .then(hotFlux.next());

        // Launch live data emission
        var livestream = hotFlux.subscribe(i -> System.out.println("Emitted: "+i));
        try {
            // Trigger value fetching after a delay
            var value = nextValue.block();
            System.out.println("next() -> " + value);
            // Immediately try to block until next value is available
            System.out.println("blockFirst() -> " + hotFlux.blockFirst());
        } finally {
            // stop live data production
            livestream.dispose();
        }
    }

    static void withBuffer() {
        // Prepare a hot flux replaying all values emitted in the past to each subscriber
        var hotFlux = Flux.interval(Duration.ofMillis(100))
                .cache();

        // Launch live data emission
        var livestream = hotFlux.subscribe(i -> System.out.println("Emitted: "+i));
        try {
            // Wait half a second, then get next emitted value.
            var value = hotFlux.skipUntilOther(Mono.delay(Duration.ofMillis(500)))
                               .next()
                               .block();
            System.out.println("skipUntilOther + next: " + value);

            // block first can also be used
            value = hotFlux.skipUntilOther(Mono.delay(Duration.ofMillis(500)))
                           .blockFirst();
            System.out.println("skipUntilOther + blockFirst: " + value);

        } finally {
            // stop live data production
            livestream.dispose();
        }
    }

    public static void blockFailsOnSomeSchedulers() throws InterruptedException {
        var hotFlux = Flux.interval(Duration.ofMillis(100)).share();

        var barrier = new CountDownLatch(1);

        var forbiddenInnerBlock = Mono.delay(Duration.ofMillis(200))
                // This block will fail, because delay op above is scheduled on parallel scheduler by default.
                .then(Mono.fromCallable(() -> hotFlux.blockFirst()))
                .doFinally(signal -> barrier.countDown());

        forbiddenInnerBlock.subscribe(value -> System.out.println("Block success: "+value),
                                      err   -> System.out.println("BLOCK FAILED: "+err.getMessage()));

        barrier.await();
    }

    static void avoidBlocking() throws InterruptedException {
        var hotFlux = Flux.interval(Duration.ofMillis(100)).share();

        var asyncValue = hotFlux.skipUntilOther(Mono.delay(Duration.ofMillis(500)))
                                .next()
                                // time wil let us verify that the value has been fetched once then cached properly
                                .timed()
                                .cache();

        asyncValue.subscribe(); // launch immediately

        // Barrier is required because we're in a main/test program. If you intend to reuse the mono in a bigger scope, you do not need it.
        CountDownLatch barrier = new CountDownLatch(2);
        // We will see that both subscribe methods return the same timestamped value, because it has been launched previously and cached
        asyncValue.subscribe(value -> System.out.println("Get value (1): "+value), err -> barrier.countDown(), () -> barrier.countDown());
        asyncValue.subscribe(value -> System.out.println("Get value (2): "+value), err -> barrier.countDown(), () -> barrier.countDown());

        barrier.await();
    }
}

該程序給出以下 output:

== 1. Hot flux without any buffering
Emitted: 0
Emitted: 1
Emitted: 2
Emitted: 3
next() -> 3
Emitted: 4
blockFirst() -> 4
== 2. Hot flux with buffering
Emitted: 0
Emitted: 1
Emitted: 2
Emitted: 3
Emitted: 4
Emitted: 5
skipUntilOther + next: 5
Emitted: 6
Emitted: 7
Emitted: 8
Emitted: 9
Emitted: 10
Emitted: 11
skipUntilOther + blockFirst: 11
== 3. block called from a wrong context
BLOCK FAILED: block()/blockFirst()/blockLast() are blocking, which is not supported in thread parallel-6
== 4. Next value without blocking
Get value (1): Timed(4){eventElapsedNanos=500247504, eventElapsedSinceSubscriptionNanos=500247504,  eventTimestampEpochMillis=1674831275873}
Get value (2): Timed(4){eventElapsedNanos=500247504, eventElapsedSinceSubscriptionNanos=500247504,  eventTimestampEpochMillis=1674831275873}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM