简体   繁体   English

发出的每个列表项的 RxJava 延迟

[英]RxJava delay for each item of list emitted

I'm struggling to implement something I assumed would be fairly simple in Rx.我正在努力实现我认为在 Rx 中相当简单的东西。

I have a list of items, and I want to have each item emitted with a delay.我有一个项目列表,我想让每个项目延迟发射。

It seems the Rx delay() operator just shifts the emission of all items by the specified delay, not each individual item.似乎 Rx delay() 运算符只是将所有项目的发射转移了指定的延迟,而不是每个单独的项目。

Here's some testing code.这是一些测试代码。 It groups items in a list.它对列表中的项目进行分组。 Each group should then have a delay applied before being emitted.然后,每个组都应该在发出之前应用延迟。

Observable.range(1, 5)
    .groupBy(n -> n % 5)
    .flatMap(g -> g.toList())
    .delay(50, TimeUnit.MILLISECONDS)
    .doOnNext(item -> {
        System.out.println(System.currentTimeMillis() - timeNow);
        System.out.println(item);
        System.out.println(" ");
    }).toList().toBlocking().first();

The result is:结果是:

154ms
[5]

155ms
[2]

155ms
[1]

155ms
[3]

155ms
[4]

But what I would expect to see is something like this:但我希望看到的是这样的:

174ms
[5]

230ms
[2]

285ms
[1]

345ms
[3]

399ms
[4]

What am I doing wrong?我究竟做错了什么?

The simplest way to do this seems to be just using concatMap and wrapping each item in a delayed Obserable.最简单的方法似乎是使用concatMap并将每个项目包装在延迟的 Obserable 中。

long startTime = System.currentTimeMillis();
Observable.range(1, 5)
        .concatMap(i-> Observable.just(i).delay(50, TimeUnit.MILLISECONDS))
        .doOnNext(i-> System.out.println(
                "Item: " + i + ", Time: " + (System.currentTimeMillis() - startTime) +"ms"))
        .toCompletable().await();

Prints:印刷:

Item: 1, Time: 51ms
Item: 2, Time: 101ms
Item: 3, Time: 151ms
Item: 4, Time: 202ms
Item: 5, Time: 252ms

One way to do it is to use zip to combine your observable with anInterval observable to delay the output.一种方法是使用zip将您的 observable 与Interval observable 组合以延迟输出。

Observable.zip(Observable.range(1, 5)
        .groupBy(n -> n % 5)
        .flatMap(g -> g.toList()),
    Observable.interval(50, TimeUnit.MILLISECONDS),
    (obs, timer) -> obs)
    .doOnNext(item -> {
      System.out.println(System.currentTimeMillis() - timeNow);
      System.out.println(item);
      System.out.println(" ");
    }).toList().toBlocking().first();

Just sharing a simple approach to emit each item in a collection with an interval:只是分享一种简单的方法来以间隔发出集合中的每个项目:

Observable.just(1,2,3,4,5)
    .zipWith(Observable.interval(500, TimeUnit.MILLISECONDS), (item, interval) -> item)
    .subscribe(System.out::println);

Each item will be emitted every 500 milliseconds每个项目将每 500 毫秒发出一次

For kotlin users, I wrote an extension function for the 'zip with interval' approach对于 kotlin 用户,我为 'zip with interval' 方法编写了一个扩展函数

import io.reactivex.Observable
import io.reactivex.functions.BiFunction
import java.util.concurrent.TimeUnit

fun <T> Observable<T>.delayEach(interval: Long, timeUnit: TimeUnit): Observable<T> =
    Observable.zip(
        this, 
        Observable.interval(interval, timeUnit), 
        BiFunction { item, _ -> item }
    )

It works the same way, but this makes it reusable.它的工作方式相同,但这使其可重用。 Example:例子:

Observable.range(1, 5)
    .delayEach(1, TimeUnit.SECONDS)

I think it's exactly what you need.我认为这正是你所需要的。 Take look:看看:

long startTime = System.currentTimeMillis();
Observable.intervalRange(1, 5, 0, 50, TimeUnit.MILLISECONDS)
                .timestamp(TimeUnit.MILLISECONDS)
                .subscribe(emitTime -> {
                    System.out.println(emitTime.time() - startTime);
                });

To introduce delay between each item emitted is useful:在发射的每个项目之间引入延迟很有用:

List<String> letters = new ArrayList<>(Arrays.asList("a", "b", "c", "d"));

Observable.fromIterable(letters)
                .concatMap(item -> Observable.interval(1, TimeUnit.SECONDS)
                        .take(1)
                        .map(second -> item))
                .subscribe(System.out::println);

More good options at https://github.com/ReactiveX/RxJava/issues/3505https://github.com/ReactiveX/RxJava/issues/3505有更多好的选择

You can implement a custom rx operator such as MinRegularIntervalDelayOperator and then use this with the lift function您可以实现自定义 rx 运算符,例如MinRegularIntervalDelayOperator ,然后将其与lift函数一起使用

Observable.range(1, 5)
    .groupBy(n -> n % 5)
    .flatMap(g -> g.toList())
    .lift(new MinRegularIntervalDelayOperator<Integer>(50L))
    .doOnNext(item -> {
      System.out.println(System.currentTimeMillis() - timeNow);
      System.out.println(item);
      System.out.println(" ");
    }).toList().toBlocking().first();
Observable.just("A", "B", "C", "D", "E", "F")
    .flatMap { item -> Thread.sleep(2000)
        Observable.just( item ) }
    .subscribe { println( it ) }

To delay each group you can change your flatMap() to return an Observable that delays emitting the group.要延迟每个组,您可以更改您的flatMap()以返回一个延迟发射组的 Observable。

Observable
        .range(1, 5)
        .groupBy(n -> n % 5)
        .flatMap(g ->
                Observable
                        .timer(50, TimeUnit.MILLISECONDS)
                        .flatMap(t -> g.toList())
        )
        .doOnNext(item -> {
            System.out.println(System.currentTimeMillis() - timeNow);
            System.out.println(item);
            System.out.println(" ");
        }).toList().toBlocking().first();

A not so clean way is to make the delay change with the iteration using the .delay(Func1) operator.一种不太干净的方法是使用 .delay(Func1) 运算符通过迭代更改延迟。

Observable.range(1, 5)
            .delay(n -> n*50)
            .groupBy(n -> n % 5)
            .flatMap(g -> g.toList())
            .doOnNext(item -> {
                System.out.println(System.currentTimeMillis() - timeNow);
                System.out.println(item);
                System.out.println(" ");
            }).toList().toBlocking().first();

There is other way to do it using concatMap as concatMap returns observable of source items.还有其他方法可以使用 concatMap 作为 concatMap 返回可观察的源项。 so we can add delay on that observable.所以我们可以在那个 observable 上添加延迟。

here what i have tried.这是我尝试过的。

Observable.range(1, 5)
          .groupBy(n -> n % 5)
          .concatMap(integerIntegerGroupedObservable ->
          integerIntegerGroupedObservable.delay(2000, TimeUnit.MILLISECONDS))
          .doOnNext(item -> {
                    System.out.println(System.currentTimeMillis() - timeNow);
                    System.out.println(item);
                    System.out.println(" ");
                }).toList().toBlocking().first(); 

You can use您可以使用

   Observable.interval(1, TimeUnit.SECONDS)
            .map(new Function<Long, Integer>() {
                @Override
                public Integer apply(Long aLong) throws Exception {
                    return aLong.intValue() + 1;
                }
            })
            .startWith(0)
            .take(listInput.size())
            .subscribe(new Consumer<Integer>() {
                @Override
                public void accept(Integer index) throws Exception {
                    Log.d(TAG, "---index of your list --" + index);
                }
            });

This code above not duplicate value(index).上面的代码不重复值(索引)。 "I'm sure" “我确定”

A Swift extension for both approaches suggested in this post.这篇文章中建议的两种方法的 Swift 扩展。

Concat康卡特

import RxSwift

extension Observable {
    public func delayEach(_ dueTime: RxSwift.RxTimeInterval, scheduler: RxSwift.SchedulerType) -> RxSwift.Observable<Element> {
        return self.concatMap { Observable.just($0).delay(dueTime, scheduler: scheduler) }
    }
}

Zip压缩

import RxSwift

extension Observable {
    public func delayEach(_ period: RxSwift.RxTimeInterval, scheduler: RxSwift.SchedulerType) -> RxSwift.Observable<Element> {
        return Observable.zip(
            Observable<Int>.interval(period, scheduler: scheduler),
            self
        ) { $1 }
    }
}

Usage用法

Observable.range(start: 1, count: 5)
    .delayEach(.seconds(1), scheduler: MainScheduler.instance)

My personal preference goes out to the concat approach since it will also work as expected when the upstream emits items at a slower rate than the delay interval.我个人偏爱 concat 方法,因为当上游以低于延迟间隔的速率发出项目时,它也会按预期工作。

And yes the original post is RxJava specific, but Google also brings you here for RxSwift queries.是的,原来的帖子是针对 RxJava 的,但谷歌也为你带来了 RxSwift 查询。

Regarding eis' comment " wonder why isn't any of the answers actually answering the question. Why isn't this working, what's wrong with it? ":关于 eis 的评论“想知道为什么没有任何答案实际上回答了这个问题。为什么这不起作用,它有什么问题? ”:

It's behaving differently than expected because delaying an item means its emission time is delayed relative to the time the item would otherwise be emitted - not relative to the previous item.它的行为与预期不同,因为延迟一个项目意味着它的发射时间相对于该项目将被发射的时间延迟 - 而不是相对于前一个项目。

Imagine the OP's observable without any delay: All items are emitted in quick succession (in the same millisecond).想象一下 OP 的 observable没有任何延迟:所有项目都快速连续发出(在同一毫秒内)。 With delay, each item is emitted later.有了延迟,每个项目都会稍后发出。 But since the same delay is applied to each item, their relative emission times do not change.但是由于相同的延迟应用于每个项目,它们的相对发射时间不会改变。 They are still emitted in one millisecond.它们仍然在一毫秒内发出。

Think of a person entering the room at 14:00.想象一个人在 14:00 进入房间。 Another person enters at 14:01.另一个人在 14:01 进入。 If you apply a delay of one hour to both, they enter at 15:00 and 15:01.如果您对两者都应用一小时的延迟,他们将在 15:00 和 15:01 进入。 There is still just one minute between them.他们之间只有一分钟的时间。

I think you want this:我想你想要这个:

Observable.range(1, 5)
            .delay(50, TimeUnit.MILLISECONDS)
            .groupBy(n -> n % 5)
            .flatMap(g -> g.toList())
            .doOnNext(item -> {
                System.out.println(System.currentTimeMillis() - timeNow);
                System.out.println(item);
                System.out.println(" ");
            }).toList().toBlocking().first();

This way it will delay the numbers going into the group rather than delaying the reduced list by 5 seconds.这样,它将延迟进入组的数字,而不是将减少的列表延迟 5 秒。

You can add a delay between emitted items by using flatMap, maxConcurrent and delay()您可以使用 flatMap、maxConcurrent 和 delay() 在发射的项目之间添加延迟

Here is an example - emit 0..4 with delay这是一个示例 - 发出 0..4 延迟

@Test
fun testEmitWithDelays() {
    val DELAY = 500L
    val COUNT = 5

    val latch = CountDownLatch(1)
    val startMoment = System.currentTimeMillis()
    var endMoment : Long = 0

    Observable
        .range(0, COUNT)
        .flatMap( { Observable.just(it).delay(DELAY, TimeUnit.MILLISECONDS) }, 1) // maxConcurrent = 1
        .subscribe(
                { println("... value: $it, ${System.currentTimeMillis() - startMoment}") },
                {},
                {
                    endMoment = System.currentTimeMillis()
                    latch.countDown()
                })

    latch.await()

    assertTrue { endMoment - startMoment >= DELAY * COUNT }
}

... value: 0, 540
... value: 1, 1042
... value: 2, 1544
... value: 3, 2045
... value: 4, 2547

you should be able to achieve this by using Timer operator.您应该能够通过使用Timer运算符来实现这一点。 I tried with delay but couldn't achieve the desired output.我尝试delay但无法达到预期的输出。 Note nested operations done in flatmap operator.请注意在flatmap运算符中完成的嵌套操作。

    Observable.range(1,5)
            .flatMap(x -> Observable.timer(50 * x, TimeUnit.MILLISECONDS)
                        .map(y -> x))
            // attach timestamp
            .timestamp()
            .subscribe(timedIntegers ->
                    Log.i(TAG, "Timed String: "
                            + timedIntegers.value()
                            + " "
                            + timedIntegers.time()));

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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