简体   繁体   English

RxJava:如果Observable是冷的,如何订阅?

[英]RxJava: How to Subscribe only if the Observable is Cold?

TL;DR: How do you create an Observable that only creates a Subscription if it is cold and queues any other subscribe calls when it is hot ? TL; DR:如何创建一个Observable,如果它是冷的 ,只创建一个Subscription,并在它很热的时候排队任何其他的subscribe调用?

I would like to create an Observable which can only execute a single Subscription at a time . 我想创建一个Observable,它一次只能执行一个Subscription If any other Subscribers subscribe to the Observable, I would like them to be queued to run when the Observable is completed (after onComplete). 如果任何其他订阅者订阅了Observable,我希望它们在Observable完成时排队等待运行(在onComplete之后)。

I can build this construct myself by having some kind of stack and popping the stack on every onComplete - but it feels like this functionality already exists in RxJava. 我可以通过在每个onComplete上使用某种堆栈并弹出堆栈来自己构建这个构造 - 但感觉这个功能已经存在于RxJava中。

Is there a way to limit subscriptions in this way? 有没有办法以这种方式限制订阅?

(More on hot and cold observables) (关于冷热观测的更多信息)

There are no built-in operators or combination of operators I can think of that can achieve this. 没有内置的运算符或我能想到的运算符组合可以实现这一目标。 Here is how I'd implement it: 这是我如何实现它:

import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.*;

import rx.*;
import rx.observers.TestSubscriber;
import rx.subjects.PublishSubject;
import rx.subscriptions.Subscriptions;

public final class SequenceSubscribers<T> implements Observable.OnSubscribe<T> {

    final Observable<? extends T> source;

    final Queue<Subscriber<? super T>> queue;

    final AtomicInteger wip;

    volatile boolean active;

    public SequenceSubscribers(Observable<? extends T> source) {
        this.source = source;
        this.queue = new ConcurrentLinkedQueue<>();
        this.wip = new AtomicInteger();
    }

    @Override
    public void call(Subscriber<? super T> t) {
        SubscriberWrapper wrapper = new SubscriberWrapper(t);
        queue.add(wrapper);

        t.add(wrapper);
        t.add(Subscriptions.create(() -> wrapper.next()));

        drain();
    }

    void complete(SubscriberWrapper inner) {
        active = false;
        drain();
    }

    void drain() {
        if (wip.getAndIncrement() != 0) {
            return;
        }
        do {
            if (!active) {
                Subscriber<? super T> s = queue.poll();
                if (s != null && !s.isUnsubscribed()) {
                    active = true;
                    source.subscribe(s);
                }
            }
        } while (wip.decrementAndGet() != 0);
    }

    final class SubscriberWrapper extends Subscriber<T> {
        final Subscriber<? super T> actual;

        final AtomicBoolean once;

        public SubscriberWrapper(Subscriber<? super T> actual) {
            this.actual = actual;
            this.once = new AtomicBoolean();
        }

        @Override
        public void onNext(T t) {
            actual.onNext(t);
        }

        @Override
        public void onError(Throwable e) {
            actual.onError(e);
            next();
        }

        @Override
        public void onCompleted() {
            actual.onCompleted();
            next();
        }

        @Override
        public void setProducer(Producer p) {
            actual.setProducer(p);
        }

        void next() {
            if (once.compareAndSet(false, true)) {
                complete(this);
            }
        }
    }

    public static void main(String[] args) {
        PublishSubject<Integer> ps = PublishSubject.create();

        TestSubscriber<Integer> ts1 = TestSubscriber.create();
        TestSubscriber<Integer> ts2 = TestSubscriber.create();

        Observable<Integer> source = Observable.create(new SequenceSubscribers<>(ps));

        source.subscribe(ts1);
        source.subscribe(ts2);

        ps.onNext(1);
        ps.onNext(2);

        ts1.assertValues(1, 2);
        ts2.assertNoValues();

        ts1.unsubscribe();

        ps.onNext(3);
        ps.onNext(4);
        ps.onCompleted();

        ts1.assertValues(1, 2);
        ts2.assertValues(3, 4);
        ts2.assertCompleted();
    }
}

I assume you have an underlying cold observable that you want to subscribe at multiple times, but only once the previous subscription finished. 我假设你有一个潜在的冷观察,你想多次订阅,但只有前一个订阅完成。

We can make smart use of the build in function delaySubscription that can delay new subscriptions. 我们可以巧妙地使用可以延迟新订阅的函数delaySubscription The next hurdle is to trigger the subscription when the previous subscription finishes. 下一个障碍是在先前订阅完成时触发订阅。 We do this with doOnUnsubscribe , that triggers both on unsubscribe, onError and onCompleted actions. 我们使用doOnUnsubscribe执行此doOnUnsubscribe ,该操作会在取消订阅, onErroronCompleted操作上触发。

public class DelaySubscribe<T> {
    Observable<Integer> previouse = Observable.just(0);

    private DelaySubscribe() {

    }

    public static <T> Observable<T> makeDelayOb(Observable<T> cold) {
        return new DelaySubscribe<T>().obs(cold);
    }

    private Observable<T> obs(Observable<T> cold) {
        return Observable.create(ob -> {
            Observable<Integer> tmp = previouse;
            ReplaySubject<Integer> rep = ReplaySubject.create();
            previouse = rep;
            cold.delaySubscription(() -> tmp).doOnUnsubscribe(() -> {
                rep.onNext(0);
                rep.onCompleted();
            }).subscribe(ob);
        });
    }

Example usage: 用法示例:

    public static void main(String[] args) throws IOException, InterruptedException {
        Observable<Long> cold = Observable.interval(1, TimeUnit.SECONDS).take(2);
        Observable<Long> hot = makeDelayOb(cold);

        Func1<Integer, rx.Observer<Long>> obs = (Integer i) -> Observers.create(el -> System.out.println(i + "next: " + el),
                er -> System.out.println(i + "error: " + er), () -> System.out.println(i + "completed"));

        System.out.println("1");
        Subscription s = hot.subscribe(obs.call(1));
        System.out.println("2");
        hot.subscribe(obs.call(2));
        Thread.sleep(1500);
        s.unsubscribe();
        System.out.println("3");
        Thread.sleep(3500);
        hot.subscribe(obs.call(3));
        System.out.println("4");
        System.in.read();
    }
}

Output: 输出:

1
2
1next: 0
3
2next: 0
2next: 1
2completed
4
3next: 0
3next: 1
3completed

I assume you have a hot observable with multiple subscriptions, but only want one subscription to receive events. 我假设您有一个具有多个订阅的热观察,但只希望一个订阅接收事件。 Once the current subscription unsubscribes, the next one should start receiving. 一旦当前订阅取消订阅,下一个订阅应该开始接收。

What we can do is give every subscription and unique number, and keep a list of all subscriptions. 我们可以做的是给每个订阅和唯一号码,并保留所有订阅的列表。 Only the first subscription in the list receives events, the rest of them filter s the events away. 只有列表中的第一个订阅接收事件,其余订阅filter掉事件。

public class SingleSubscribe {
    List<Integer> current = Collections.synchronizedList(new ArrayList<>());
    int max = 0;
    Object gate = new Object();

    private SingleSubscribe() {
    }

    public static <T> Transformer<T, T> singleSubscribe() {
        return new SingleSubscribe().obs();
    }

    private <T> Transformer<T, T> obs() {
        return (source) -> Observable.create((Subscriber<? super T> ob) -> {
            Integer me;
            synchronized (gate) {
                 me = max++;
            }
            current.add(me);
            source.doOnUnsubscribe(() -> current.remove(me)).filter(__ -> {
                return current.get(0) == me;
            }).subscribe(ob);
        });
    }

Example usage: 用法示例:

    public static void main(String[] args) throws InterruptedException, IOException {
        ConnectableObservable<Long> connectable = Observable.interval(500, TimeUnit.MILLISECONDS)
                .publish();
        Observable<Long> hot = connectable.compose(SingleSubscribe.<Long> singleSubscribe());
        Subscription sub = connectable.connect();

        Func1<Integer, rx.Observer<Long>> obs = (Integer i) -> Observers.create(el -> System.out.println(i + "next: " + el),
                er -> {
                    System.out.println(i + "error: " + er);
                    er.printStackTrace();
                } , () -> System.out.println(i + "completed"));

        System.out.println("1");
        Subscription s = hot.subscribe(obs.call(1));
        System.out.println("2");
        hot.take(4).subscribe(obs.call(2));
        Thread.sleep(1500);
        s.unsubscribe();
        System.out.println("3");
        Thread.sleep(500);
        hot.take(2).subscribe(obs.call(3));
        System.out.println("4");
        System.in.read();
        sub.unsubscribe();
    }
}

Output: 输出:

1
2
1next: 0
1next: 1
1next: 2
3
2next: 3
4
2next: 4
2next: 5
2next: 6
2completed
3next: 6
3next: 7
3completed

Notice there is a small flaw in the output: Because 2 unsubscribes right after it receives 6, but before 3 receives 6. After 2 unsubscribes, 3 is the next active observer and happily accept 6. 请注意,输出中存在一个小缺陷:因为2在取得6之后立即取消订阅,但在3之前接收6次。在2次取消订阅之后,3是下一个活跃的观察者并愉快地接受6。

A solution would be to delay doOnUnsubscribe , easiest way is to schedule the action on a new thread scheduler: 解决方案是延迟doOnUnsubscribe ,最简单的方法是在新的线程调度程序上安排操作:

source.doOnUnsubscribe(() -> Schedulers.newThread().createWorker().schedule(()-> current.remove(me)))

However, this means there is now a small chance the next item emitted is ignored when 2 unsubscribe, and the next item arrives before 3 is activated. 但是,这意味着当2次取消订阅时,下一个项目被忽略,并且下一个项目在激活3之前到达时,现在很有可能。 Use the variation that is most suitable for you. 使用最适合您的变体。

Lastly, this solution absolutely assumes the source to be hot. 最后,这个解决方案绝对假设源是热的。 I'm not sure what will happen when you apply this operator to a cold observable, but the results could be unexpected. 我不确定将此运算符应用于冷可观察量时会发生什么,但结果可能是意外的。

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

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