Rxjava thread switching principle

Keywords: Java Android rxjava

preface

Rxjava can easily switch threads, so how does rxjava switch threads? Read this article to learn how rxjava performs thread switching and the impact of thread switching.

A simple code:

Observable.create(new ObservableOnSubscribe<String>() {
    @Override
    public void subscribe(ObservableEmitter<String> e) throws Exception {
        Log.d("WanRxjava ", "subscrib  td ==" + Thread.currentThread().getName());
        e.onNext("I'm sending next");
        e.onComplete();
    }
}).subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Observer<String>() {
            @Override
            public void onSubscribe(Disposable d) {
                Log.d("WanRxjava ", "onSubscribe td ==" + Thread.currentThread().getName());
            }

            @Override
            public void onNext(String value) {
                Log.d("WanRxjava ", "onNext td ==" + Thread.currentThread().getName());
            }

            @Override
            public void onError(Throwable e) {

            }

            @Override
            public void onComplete() {
                Log.d("WanRxjava ", "onComplete td ==" + Thread.currentThread().getName());
            }
        });

The above code implements the logic of thread switching and binding the observer to the observed. Let's look at the above code logic in four parts: create, subscribeOn, observeOn and subscribe

1.create

As the name suggests, create is to create an observer. Here, a parameter is ObservableOnSubscribe, which is an interface class. Let's look at the source code of create:

@SchedulerSupport(SchedulerSupport.NONE)
public static <T> Observable<T> create(ObservableOnSubscribe<T> source) {
    ObjectHelper.requireNonNull(source, "source is null");
    return RxJavaPlugins.onAssembly(new ObservableCreate<T>(source));
}


After the ObservableOnSubscribe is passed in, new ObservableCreate(source) is called

public final class ObservableCreate<T> extends Observable<T> {
    final ObservableOnSubscribe<T> source;

    public ObservableCreate(ObservableOnSubscribe<T> source) {
        this.source = source;
    }
}

One variable of ObservableCreate is source. Here, we just assign the incoming ObservableOnSubscribe to source, that is, make a layer of packaging, and then return.

2.subscribeOn

After calling create, it returns ObservableCreate (Observable), and then continues to call subscribeOn, passing in a variable Schedulers.io()

@SchedulerSupport(SchedulerSupport.CUSTOM)
public final Observable<T> subscribeOn(Scheduler scheduler) {
    ObjectHelper.requireNonNull(scheduler, "scheduler is null");
    return RxJavaPlugins.onAssembly(new ObservableSubscribeOn<T>(this, scheduler));
}

We see that new ObservableSubscribeOn(this, scheduler) is called to pass in itself and the scheduler

public final class ObservableSubscribeOn<T> extends AbstractObservableWithUpstream<T, T> {
    final Scheduler scheduler;

    public ObservableSubscribeOn(ObservableSource<T> source, Scheduler scheduler) {
        super(source);
        this.scheduler = scheduler;
    }
}

ObservableSubscribeOn wraps up the objects returned by scheduler and create, and returns ObservableSubscribeOn

3.observeOn

One parameter is Scheduler

@SchedulerSupport(SchedulerSupport.CUSTOM)
public final Observable<T> observeOn(Scheduler scheduler) {
    return observeOn(scheduler, false, bufferSize());
}
@SchedulerSupport(SchedulerSupport.CUSTOM)
public final Observable<T> observeOn(Scheduler scheduler, boolean delayError, int bufferSize) {
    ObjectHelper.requireNonNull(scheduler, "scheduler is null");
    ObjectHelper.verifyPositive(bufferSize, "bufferSize");
    return RxJavaPlugins.onAssembly(new ObservableObserveOn<T>(this, scheduler, delayError, bufferSize));
}

ObservableSubscribeOn (observable) also calls observeOn, and then calls new ObservableObserveOn(this, scheduler, delayError, bufferSize).

public final class ObservableObserveOn<T> extends AbstractObservableWithUpstream<T, T> {
    final Scheduler scheduler;
    final boolean delayError;
    final int bufferSize;
    public ObservableObserveOn(ObservableSource<T> source, Scheduler scheduler, boolean delayError, int bufferSize) {
        super(source);
        this.scheduler = scheduler;
        this.delayError = delayError;
        this.bufferSize = bufferSize;
    }
}

It is also a wrapper that wraps ObservableSubscribeOn and scheduler into ObservableObserveOn

4.subscribe

The last step above is to call ObservableObserveOn.subscribe, and the passed in parameter is an observer

//ObservableObserveOn.java
@SchedulerSupport(SchedulerSupport.NONE)
@Override
public final void subscribe(Observer<? super T> observer) {
    ObjectHelper.requireNonNull(observer, "observer is null");
    try {
        observer = RxJavaPlugins.onSubscribe(this, observer);

        ObjectHelper.requireNonNull(observer, "Plugin returned null Observer");

        subscribeActual(observer);
    } catch (NullPointerException e) { // NOPMD
        throw e;
    } catch (Throwable e) {
        Exceptions.throwIfFatal(e);
        // can't call onError because no way to know if a Disposable has been set or not
        // can't call onSubscribe because the call might have set a Subscription already
        RxJavaPlugins.onError(e);

        NullPointerException npe = new NullPointerException("Actually not, but can't throw other exceptions due to RS");
        npe.initCause(e);
        throw npe;
    }
}

You can see that after calling subscribe, you call subscribeActual(observer). Pass observer in

Let's take a look at subscribe actual (observer)

//ObservableObserveOn.java
@Override
protected void subscribeActual(Observer<? super T> observer) {
    if (scheduler instanceof TrampolineScheduler) {
        source.subscribe(observer);
    } else {
        Scheduler.Worker w = scheduler.createWorker();

        source.subscribe(new ObserveOnObserver<T>(observer, w, delayError, bufferSize));
    }
}

The above if doesn't care. It mainly looks at the following logic and calls scheduler.createWorker(). This scheduler is imported by observeOn and then called.

new ObserveOnObserver(observer, w, delayError, bufferSize); Wrap the worker /observer again.

//ObservableObserveOn inner class
static final class ObserveOnObserver<T> extends BasicIntQueueDisposable<T>
implements Observer<T>, Runnable {

    private static final long serialVersionUID = 6576896619930983584L;
    final Observer<? super T> actual;
    final Scheduler.Worker worker;
    final boolean delayError;
    final int bufferSize;

    SimpleQueue<T> queue;

    Disposable s;

    Throwable error;
    volatile boolean done;

    volatile boolean cancelled;

    int sourceMode;

    boolean outputFused;

    ObserveOnObserver(Observer<? super T> actual, Scheduler.Worker worker, boolean delayError, int bufferSize) {
        this.actual = actual;
        this.worker = worker;
        this.delayError = delayError;
        this.bufferSize = bufferSize;
    }

    @Override
    public void onSubscribe(Disposable s) {
        if (DisposableHelper.validate(this.s, s)) {
            this.s = s;
            if (s instanceof QueueDisposable) {
                @SuppressWarnings("unchecked")
                QueueDisposable<T> qd = (QueueDisposable<T>) s;

                int m = qd.requestFusion(QueueDisposable.ANY | QueueDisposable.BOUNDARY);

                if (m == QueueDisposable.SYNC) {
                    sourceMode = m;
                    queue = qd;
                    done = true;
                    actual.onSubscribe(this);
                    schedule();
                    return;
                }
                if (m == QueueDisposable.ASYNC) {
                    sourceMode = m;
                    queue = qd;
                    actual.onSubscribe(this);
                    return;
                }
            }

            queue = new SpscLinkedArrayQueue<T>(bufferSize);

            actual.onSubscribe(this);
        }
    }

    @Override
    public void onNext(T t) {
        if (done) {
            return;
        }

        if (sourceMode != QueueDisposable.ASYNC) {
            queue.offer(t);
        }
        schedule();
    }

    @Override
    public void onError(Throwable t) {
        if (done) {
            RxJavaPlugins.onError(t);
            return;
        }
        error = t;
        done = true;
        schedule();
    }

    @Override
    public void onComplete() {
        if (done) {
            return;
        }
        done = true;
        schedule();
    }

    @Override
    public void dispose() {
        if (!cancelled) {
            cancelled = true;
            s.dispose();
            worker.dispose();
            if (getAndIncrement() == 0) {
                queue.clear();
            }
        }
    }

    @Override
    public boolean isDisposed() {
        return cancelled;
    }

    void schedule() {
        if (getAndIncrement() == 0) {
            worker.schedule(this);
        }
    }

    void drainNormal() {
        int missed = 1;

        final SimpleQueue<T> q = queue;
        final Observer<? super T> a = actual;

        for (;;) {
            if (checkTerminated(done, q.isEmpty(), a)) {
                return;
            }

            for (;;) {
                boolean d = done;
                T v;

                try {
                    v = q.poll();
                } catch (Throwable ex) {
                    Exceptions.throwIfFatal(ex);
                    s.dispose();
                    q.clear();
                    a.onError(ex);
                    return;
                }
                boolean empty = v == null;

                if (checkTerminated(d, empty, a)) {
                    return;
                }

                if (empty) {
                    break;
                }

                a.onNext(v);
            }

            missed = addAndGet(-missed);
            if (missed == 0) {
                break;
            }
        }
    }

    void drainFused() {
        int missed = 1;

        for (;;) {
            if (cancelled) {
                return;
            }

            boolean d = done;
            Throwable ex = error;

            if (!delayError && d && ex != null) {
                actual.onError(error);
                worker.dispose();
                return;
            }

            actual.onNext(null);

            if (d) {
                ex = error;
                if (ex != null) {
                    actual.onError(ex);
                } else {
                    actual.onComplete();
                }
                worker.dispose();
                return;
            }

            missed = addAndGet(-missed);
            if (missed == 0) {
                break;
            }
        }
    }

    @Override
    public void run() {
        if (outputFused) {
            drainFused();
        } else {
            drainNormal();
        }
    }

    boolean checkTerminated(boolean d, boolean empty, Observer<? super T> a) {
        if (cancelled) {
            queue.clear();
            return true;
        }
        if (d) {
            Throwable e = error;
            if (delayError) {
                if (empty) {
                    if (e != null) {
                        a.onError(e);
                    } else {
                        a.onComplete();
                    }
                    worker.dispose();
                    return true;
                }
            } else {
                if (e != null) {
                    queue.clear();
                    a.onError(e);
                    worker.dispose();
                    return true;
                } else
                if (empty) {
                    a.onComplete();
                    worker.dispose();
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public int requestFusion(int mode) {
        if ((mode & ASYNC) != 0) {
            outputFused = true;
            return ASYNC;
        }
        return NONE;
    }

    @Override
    public T poll() throws Exception {
        return queue.poll();
    }

    @Override
    public void clear() {
        queue.clear();
    }

    @Override
    public boolean isEmpty() {
        return queue.isEmpty();
    }
}

After wrapping ObserveOnObserver, the source.subscribe source here is called ObservableSubscribeOn.subscribe, and then ObservableSubscribeOn.subscribeActual is invoked.

//ObservableSubscribeOn.java
@Override
public void subscribeActual(final Observer<? super T> s) {
    final SubscribeOnObserver<T> parent = new scheduler<T>(s);

    s.onSubscribe(parent);

    parent.setDisposable(scheduler.scheduleDirect(new Runnable() {
        @Override
        public void run() {
            source.subscribe(parent);
        }
    }));
}

static final class SubscribeOnObserver<T> extends AtomicReference<Disposable> implements Observer<T>, Disposable {

    private static final long serialVersionUID = 8094547886072529208L;
    final Observer<? super T> actual;

    final AtomicReference<Disposable> s;

    SubscribeOnObserver(Observer<? super T> actual) {
        this.actual = actual;
        this.s = new AtomicReference<Disposable>();
    }

    @Override
    public void onSubscribe(Disposable s) {
        DisposableHelper.setOnce(this.s, s);
    }

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

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

    @Override
    public void onComplete() {
        actual.onComplete();
    }

    @Override
    public void dispose() {
        DisposableHelper.dispose(s);
        DisposableHelper.dispose(this);
    }

    @Override
    public boolean isDisposed() {
        return DisposableHelper.isDisposed(get());
    }

    void setDisposable(Disposable d) {
        DisposableHelper.setOnce(this, d);
    }
}

ObservableSubscribeOn.subscribeActual

First, the incoming observer is encapsulated as SubscribeOnObserver
Then, onSubscribe is triggered, and then scheduler.scheduleDirect(new Runnable() is called. The scheduler here is passed in by subscribeOn
Finally, the scheduler.setsetDisposable method is invoked.

We see the method body of run is source.subscribe(parent); the source here is ObservableCreate (ObservableOnSubscribe), which is passed to observer, and then invokes observer's OnNext and OnComplete methods.

5. Summary:
a. Calling the Observer.OnSubscribe method is not affected by thread scheduling
b.subscribeOn affects the thread that sends the event
c.observerOn affects the thread that the observer processes and receives data. If observeOn is not called, it will not be packaged as ObserveOnObserver, that is, the observer's thread switching will not be executed, which is consistent with the sender's thread
d. When subscribeOn is called several times to switch threads, new ObservableSubscribeOn will be called each time. When the event is triggered, it will be called up, that is, the thread passed in by subscribeOn called for the first time will execute the send event, and the subsequent thread switching is invalid
e.Observer.OnSubscribe is executed only once because DisposableHelper.setOnce(this.s, s) is called
f. After onComplete or onError is processed, the event will not be issued again, because the observer will call disposed after sending these two events

Relevant video recommendations

How does thread switching in rxjava intervene in the original framework
Zero foundation of Android development from entry to mastery

This article is transferred from https://juejin.cn/post/6952831553349091358 , in case of infringement, please contact to delete.

Posted by Ruchi on Thu, 02 Dec 2021 13:31:04 -0800