「RxJava 3.x subscribeOn スレッド切り替え解析」
Kotlin コルーチン
Kotlinのコルーチンがどんどん安定してきているので、新しいプロジェクトは基本的にコルーチンに切り替わったと言えますが、古いプロジェクトは両者が共存している状態にあると言え、Androidプロジェクトに関する限り、それは予見できます。コルーチンがRxJava を「置き換える」のは時間の問題です。コルーチン自体の利点は確かに非常に香りがよく、プロジェクトの開発を満足させることができます。最も重要なことは、使いやすさです。
RxJava
独自のレスポンシブ プログラミングの啓蒙として、 RxJava は依然として非常に優れたレスポンシブ プログラミング フレームワークであり、最新バージョンは に更新されています。3.1.6コールバック地獄を初めて見たときの驚きが懐かしいです。当時はよく知っていたのですが、結局ディープシンキングをしっかりと勉強していませんでした. 古いプロジェクトは現在、RxJavaとコルーチンが共存する状態になっているので、私もRxJavaを学び直したいと思っています. RxJavaとコルーチンの間にはまだ多くの共通点があります。
購読オン
RxJava は非同期操作を解決するために生まれました。これは、Schedulersを介した内部スレッドの切り替えに依存しています。呼び出し元は、内部実装がどのように実装されているかを「気にする」必要はなく、チェーンでsubscribeOn(xxx)を直接呼び出してスレッドを指定し、スレッドの切り替えを完了することができます。では、最下層はどのように実装されているのでしょうか? 別の質問: subscribeOnへの複数の呼び出しの場合、最初の呼び出しのみが有効になるのはなぜですか? この 2 つの質問でソース コードの実装を見てみましょう。
subscribeOn を複数回呼び出す
直接作成の分析をスキップします. Observableの作成は比較的簡単です. オペレータを作成することにより, 例として文字ウィンドウを作成します. 次のように書くことができます:
private static void subscribeOnTest() {
Observable.just("Hello World")
.subscribeOn(Schedulers.newThread())
.subscribeOn(Schedulers.io())
.subscribeOn(Schedulers.single())
.subscribe(new Consumer<String>() {
@Override
public void accept(String result) throws Throwable {
printThreadInfo();
System.out.println(result);
}
})
sleep(10000L);
}
private static void sleep(long millis) {
if (millis < 0) return;
try {
Thread.sleep(millis);
} catch () {
e.printStackTrace();
}
}
private static void printThreadInfo() {
Thread thread = Thread.currentThread();
System.out.println("当前线程名称 = " + thread.getName() + "\n" + "当前线程信息 = " + thread);
}
复制代码
情報を出力し、 subscribeOn を複数回呼び出し、Schedulers を介して異なるスレッド操作データを指定するための簡単なテスト メソッドです。ここでのスリープの目的は、テスト コードに操作を完了するのに十分な時間を与えることですが、もちろん、このステップが必要かどうかは実際の状況によって異なります。このテストのIDEAとハードウェアでは、待ち時間が追加されていないと、情報を出力できない場合があります。RxJava公式サイトにも解決策があります。githubの回答からの引用:
When you use the default scheduler (
Schedulers.computation()
) the observable emits on another thread. If your program exits just after thesubscribe
then the observable is not given a chance to run. Put in a long sleep just after thesubscribe()
call and you will see it working.The
immediate()
scheduler is not safe for recursive scheduling on the current thread, usetrampoline()
instead.
回到正题。首先需要明确RxJava几个重要“成员”之间的关系。
1. Observabel、Observer、Subscribe
Observable,顾名思义“被观察者”,Observer则是“观察者”;Subscribe订阅操作。有被观察者、观察者,那么需要将它们之间建立关系就必须要订阅这个操作,而subscribe就是让Observable和
Observer建立这种关系的,回忆平时项目代码所写的观察者模式,一般会需要将观察的接口添加到需要观察数据变动的实现类中:
interface Callback {
void onNotify(String result);
}
//...
private void adCallback(Callback callback) {
//....
}
复制代码
对于RxJava的链式调用可以简化为:
Observable.just("Hello World").subscribe(observer)
复制代码
按照平时的思维习惯,应该是观察者来订阅被观察者,而从代码所传递的信息上好像是写“反了”,有点别扭,其实也很好理解,意思是一样的:被观察者被观察者订阅。看上面的测试代码,当真正执行到subscribe两者建立关系时才真正是数据流转,线程切换等一些列操作。但前提是冷流。一步步分析内部是怎么执行的。
2. Observable#subscribe
看一下Observable的方法subscribe
@CheckReturnValue
@SchedulerSupport(SchedulerSupport.NONE)
@NonNull
public final Disposable subscribe(@NonNull Consumer<? super T> onNext) {
return subscribe(onNext, Functions.ON_ERROR_MISSING, Functions.EMPTY_ACTION);
}
复制代码
可以看到这里需要的是一个参数Consumer<? super T> onNext,其实就上游数据发送处理后最终回调,说白了就是结果,用来展示刷新UI。具体的实现为:
public final Disposable subscribe(@NonNull Consumer<? super T> onNext, @NonNull Consumer<? super Throwable> onError,
@NonNull Action onComplete) {
Objects.requireNonNull(onNext, "onNext is null");
Objects.requireNonNull(onError, "onError is null");
Objects.requireNonNull(onComplete, "onComplete is null");
LambdaObserver<T> ls = new LambdaObserver<>(onNext, onError, onComplete, Functions.emptyConsumer());
subscribe(ls);
return ls;
}
复制代码
这个方法里LambdaObserver,是对Observer的一层包装,包含结果、异常、onComplete。重点需要看的是subscribe(ls),本质就是通过这里完成订阅建立联系。
public final void subscribe(@NonNull Observer<? super T> observer) {
Objects.requireNonNull(observer, "observer is null");
try {
observer = RxJavaPlugins.onSubscribe(this, observer);
Objects.requireNonNull(observer, "The RxJavaPlugins.onSubscribe hook returned a null Observer. Please change the handler provided to RxJavaPlugins.setOnObservableSubscribe for invalid null returns. Further reading: https://github.com/ReactiveX/RxJava/wiki/Plugins");
subscribeActual(observer);
}
}
//省略了异常捕捉信息
复制代码
这里还是没有具体的实现,但是已经能看到subscribeActual(observer),可以猜测这个方法应该就是真正订阅的方法了。
3. Observable#subscribeActual(observer)
Observable中subscribeActual()方法是抽象的,那么就需要找到具体的实现类,debug模式下可以跟踪到实现类ObservableSubscribeOn,这个类间接继承了Observale抽象类,而**subscribeActual()**具体实现就在这里。代码比较精简,只有一百行,但是确能解释之前提到的问题,直接贴出代码:
package io.reactivex.rxjava3.internal.operators.observable;
import java.util.concurrent.atomic.AtomicReference;
import io.reactivex.rxjava3.core.*;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.internal.disposables.DisposableHelper;
public final class ObservableSubscribeOn<T> extends AbstractObservableWithUpstream<T, T> {
final Scheduler scheduler;
public ObservableSubscribeOn(ObservableSource<T> source, Scheduler scheduler) {
super(source);
this.scheduler = scheduler;
}
//subscribe的具体实现方法
@Override
public void subscribeActual(final Observer<? super T> observer) {
final SubscribeOnObserver<T> parent = new SubscribeOnObserver<>(observer);
observer.onSubscribe(parent);
parent.setDisposable(scheduler.scheduleDirect(new SubscribeTask(parent)));
}
final class SubscribeTask implements Runnable {
private final SubscribeOnObserver<T> parent;
SubscribeTask(SubscribeOnObserver<T> parent) {
this.parent = parent;
}
@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> downstream;
final AtomicReference<Disposable> upstream;
SubscribeOnObserver(Observer<? super T> downstream) {
this.downstream = downstream;
this.upstream = new AtomicReference<>();
}
@Override
public void onSubscribe(Disposable d) {
DisposableHelper.setOnce(this.upstream, d);
}
@Override
public void onNext(T t) {
downstream.onNext(t);
}
@Override
public void onError(Throwable t) {
downstream.onError(t);
}
@Override
public void onComplete() {
downstream.onComplete();
}
@Override
public void dispose() {
DisposableHelper.dispose(upstream);
DisposableHelper.dispose(this);
}
@Override
public boolean isDisposed() {
return DisposableHelper.isDisposed(get());
}
void setDisposable(Disposable d) {
DisposableHelper.setOnce(this, d);
}
}
}
复制代码
看关键代码 subscribeActual(final Observer<? super T> observer) 的第一行:
final SubscribeOnObserver<T> parent = new SubscribeOnObserver<>(observer);
复制代码
这里传入的观察者observer就是上述测试代码的回调Consumer,通过这个observer又重新构造了一个SubscribeOnObserver对象,并且命名为parent;这个命名也是有讲究的。这个放到后面在讲。看一下SubscribeOnObserver的构造函数:
SubscribeOnObserver(Observer<? super T> downstream) {
this.downstream = downstream;
this.upstream = new AtomicReference<>();
}
复制代码
很好理解观察者observer,就是下游的数据downstream,同理上游是谁?就是被观察者Observable,不过此时上游的数据还没有下发下来。总结一句话:利用下游的observer重新构建了一个SubscribeOnObserver,其实本质还是observer,只不过新构建的这个变成parent?What the hell?;这是什么操作?不按常理出牌啊,不应该是child才对嘛?其实不然,从这里其实可以猜到一点了,我们知道的是数据从上游发送到下游。数据的处理却是从下游到上游的。稍后验证,看看第二行关键代码:
observer.onSubscribe(parent);
复制代码
回忆之前observable#subscribe方法,从订阅开始,其实是利用Consumer创建了一个LambdaObserver对象,也即是这里的observer,所以去到LambdaObserver类中看看LambdaObserver#onSubscribe的具体实现:
observer.onSubscribe(parent);
final SubscribeOnObserver<T> parent = new SubscribeOnObserver<>(observer);
//...
@Override
public void onSubscribe(Disposable d) {
if (DisposableHelper.setOnce(this, d)) {
try {
onSubscribe.accept(this);
} catch (Throwable ex) {
Exceptions.throwIfFatal(ex);
d.dispose();
onError(ex);
}
}
}
//...
public static boolean setOnce(AtomicReference<Disposable> field, Disposable d) {
Objects.requireNonNull(d, "d is null");
if (!field.compareAndSet(null, d)) {
d.dispose();
if (field.get() != DISPOSED) {
reportDisposableSet();
}
return false;
}
return true;
}
复制代码
需要注意的是,由于此时均还是默认创建的observer,比较简单直接走到方法内部onSubscribe.accept(this);。直接看最后一行的行为:
parent.setDisposable(scheduler.scheduleDirect(new SubscribeTask(parent)));
复制代码
从创建SubscribeTask开始,分析一下这个task是干嘛用的:
final class SubscribeTask implements Runnable {
private final SubscribeOnObserver<T> parent;
SubscribeTask(SubscribeOnObserver<T> parent) {
this.parent = parent;
}
@Override
public void run() {
source.subscribe(parent);
}
}
复制代码
实现了Runnable接口,也就是为开了一个子线程来执行任务,重要的是用来执行什么任务的?也就是source.subscribe(parent);,这里的parent好理解,上面已经解释过了,至于source是什么,先放放。目前仅仅需要了解的它就是一个线程任务,那么这个任务的执行时机是什么时候呢?
parent.setDisposable(scheduler.scheduleDirect(new SubscribeTask(parent)));
复制代码
在看scheduleDirect,调用者是scheduler,也就是线程调度器。scheduleDirect翻译过来的字面意思就是直接“执行”。看看实现是不是这样,找到Ssheduler#scheduleDirect:
@NonNull
public Disposable scheduleDirect(@NonNull Runnable run) {
return scheduleDirect(run, 0L, TimeUnit.NANOSECONDS);
}
@NonNull
public Disposable scheduleDirect(@NonNull Runnable run, long delay, @NonNull TimeUnit unit) {
final Worker w = createWorker();
final Runnable decoratedRun = RxJavaPlugins.onSchedule(run);
DisposeTask task = new DisposeTask(decoratedRun, w);
w.schedule(task, delay, unit);
return task;
}
//worker 为Scheduler中的抽象类
复制代码
和分析的是一样这个方法就是让task直接运行,执行什么?就是SubscribeTask#run的实现也就是source.subscribe(parent);。而调用者也知道了就是scheduler:
parent.setDisposable(scheduler.scheduleDirect(new SubscribeTask(parent)));
复制代码
但是scheduler是抽象的,所以需要知道它的实现类,其实就是**scheduler.io()**等对应的那些操作,主要实现类:
//ComputationScheduler
//ExecutorScheduler
//ImmediateThinScheduler
//IoScheduler
//NewThreadScheduler
//SchedulerWhen
//SingleScheduler
//TestScheduler
//TrampolineScheduler
复制代码
到这里基本就要搞清楚scheduler的实际实现类就清楚了。回到开始测试的代码:
Observable.just("Hello World").subscribeOn(Schedulers.newThread())..subscribeOn(Schedulers.io())
public final Observable<T> subscribeOn(@NonNull Scheduler scheduler) {
Objects.requireNonNull(scheduler, "scheduler is null");
return RxJavaPlugins.onAssembly(new ObservableSubscribeOn<>(this, scheduler));
}
public ObservableSubscribeOn(ObservableSource<T> source, Scheduler scheduler) {
super(source);
this.scheduler = scheduler;
}
复制代码
可就清晰了,当调用到最后一个 .subscribeOn(Schedulers.single()) 此时的scheduler 已经是 Schedulers.single() 了;那么问题来了线程是怎么从 single 一步步切换到第一次的调用 newThread() 的呢?将这个链式切换线程的方法展开从订阅的动作触发开始就相当于(回忆上述的parent)将数字越大的则记为“辈分”越小,那么整个流程展开的话就是
Observer observer4 = new LambdaObserver()
SubscribeOnObserver parent3 = new SubscribeOnObserver(observer4)
//通过Runnable任务
parent3.setDisposable(scheduler.scheduleDirect(new SubscribeTask(parent3)));
--> 这个方法的调用者是ObservableSubscribeOn也就是可以理解为observable 进一步简化为
--> observable2.subscribeActual(parent3) 从头开始就是
Observer observer4 = new LambdaObserver()
observable3.subscribeActual(observer4) [SubscribeOnObserver parent3 = new SubscribeOnObserver(observer4)]
observable2.subscribeActual(parent3)
observable1.subscribeActual(parent2)
observable.subscribeActual(parent1)
//这样整个流程就完成了从最下游的 Schedulers.single(),递归的到了第一次的Schedulers.newThread()
复制代码
伪代码
注意到ObservableSubscribeOn
的抽象父类AbstractObservableWithUpstream
除了继承了Observable
;还实现了接口HasUpstreamObservableSource
,这个接口很好解释了SubscribeTask
是如何工作流转的。看看这个接口的解释:
//Interface indicating the implementor has an upstream ObservableSource-like source available //via {@link #source()} method.
public interface HasUpstreamObservableSource<@NonNull T> {
//Returns the upstream source of this Observable.
//Allows discovering the chain of observables.
//@return the source ObservableSource
@NonNull
ObservableSource<T> source();
}
复制代码
- AbstractObservableWithUpstream
abstract class AbstractObservableWithUpstream<T, U> extends Observable<U> implements HasUpstreamObservableSource<T> {
/** The source consumable Observable. */
protected final ObservableSource<T> source;
/**
* Constructs the ObservableSource with the given consumable.
* @param source the consumable Observable
*/
AbstractObservableWithUpstream(ObservableSource<T> source) {
this.source = source;
}
@Override
public final ObservableSource<T> source() {
return source;
}
}
复制代码
那么就比较清晰了,每次创建新的ObservableSubscribeOn
;既然是抽象的继承关系,必然会先走的父类的构造方法,而父类中又持有了上游的upStream
通过接口HasUpstreamObservableSource
。通过伪代码展示一下是不是这样,剔除其他所有方法,仅仅保留这个source
。
public interface HasUpstreamObservableSource {
ObservableSource source();
}
public abstract class AbstractObservableWithUpstream extends Observable implements HasUpstreamObservableSource {
protected final ObservableSource source;
AbstractObservableWithUpstream(ObservableSource source) {
this.source = source;
}
@Override
public ObservableSource source() {
return source;
}
}
public abstract class Observable implements ObservableSource {
@Override
public final void subscribe(Observer observer) {
}
}
interface ObservableSource {
void subscribe(Observer observer);
}
public class ObservableSourceImpl implements ObservableSource {
public ObservableSourceImpl() {
}
@Override
public void subscribe(Observer observer) {
}
}
public class ObservableSubscribeOn extends AbstractObservableWithUpstream {
public ObservableSubscribeOn(ObservableSource source) {
super(source);
}
static final class SubscribeOnObserver implements Observer {
SubscribeOnObserver () {
}
@Override
public void onSubscribe() {
}
@Override
public void onNext() {
}
@Override
public void onError() {
}
@Override
public void onComplete() {
}
}
final class SubscribeTask implements Runnable {
private final SubscribeOnObserver parent;
SubscribeTask(SubscribeOnObserver parent) {
this.parent = parent;
}
@Override
public void run() {
source.subscribe(parent);
}
}
@Override
public String toString() {
return super.toString();
}
}
interface Observer {
void onSubscribe();
void onNext();
void onError();
void onComplete();
}
复制代码
- 测试打印信息
private static void test() {
ObservableSourceImpl observableRoot = new ObservableSourceImpl();
System.out.println("observableRoot = " + observableRoot);
ObservableSubscribeOn one = new ObservableSubscribeOn(observableRoot);
System.out.println("one = " + one);
ObservableSubscribeOn two = new ObservableSubscribeOn(one);
System.out.println("two = " + two);
ObservableSubscribeOn three = new ObservableSubscribeOn(two);
System.out.println("three = " + three);
ObservableSubscribeOn four = new ObservableSubscribeOn(three);
System.out.println("four = " + four);
System.out.println("------------>\n");
System.out.println("four 前置 ObservableSource = " + four.source());
System.out.println("three 前置 ObservableSource = " + three.source());
System.out.println("two 前置 ObservableSource = " + two.source());
System.out.println("one 前置 ObservableSource = " + one.source());
}
//打印信息
> Task :main()
observableRoot = io.reactivex.rxjava3.simple.ObservableSourceImpl@1c4af82c
one = io.reactivex.rxjava3.simple.ObservableSubscribeOn@76ccd017
two = io.reactivex.rxjava3.simple.ObservableSubscribeOn@182decdb
three = io.reactivex.rxjava3.simple.ObservableSubscribeOn@26f0a63f
four = io.reactivex.rxjava3.simple.ObservableSubscribeOn@4361bd48
------------>
four 前置 ObservableSource = io.reactivex.rxjava3.simple.ObservableSubscribeOn@26f0a63f
three 前置 ObservableSource = io.reactivex.rxjava3.simple.ObservableSubscribeOn@182decdb
two 前置 ObservableSource = io.reactivex.rxjava3.simple.ObservableSubscribeOn@76ccd017
one 前置 ObservableSource = io.reactivex.rxjava3.simple.ObservableSourceImpl@1c4af82c
复制代码
可以看到的是,每一个ObservableSource
都持有了它的上游ObservableSource
,就是套娃,而持有的目的是什么呢?想想之前的SubscribeTask
中的实现:
source.subscribe(parent);
复制代码
这里的source
就是对其上游的引用,那就好理解了,这个task
任务就是为了激活它的上游。因此回忆之前的just
发射数据,订阅之前的最后一次调用subscribeOn(Schedulers.single())
,那么这个task
在哪里执行呢,这个跟被包装的Schedulers
有关,这个因为不仅仅能拿到source
信息,同样可以拿到schedulers
的信息。知道遇到真正发射数据的observable
,所以这个时候就意味着整个由下至上的流程就结束了,也就是只有第一次的设置是“有效的”。其他的不是没有效果,只是对于用户无感知。
总结
整个流程下来可以清晰的看到,当被观察者Observable,被创建出来是,通过subscribeOn多次调用切换线程,最终被订阅subscribe到观察者上,但是处理订阅流是从下而上的,类似递归的方式,将observable一直传到最下游,每一级的结果依赖下层的返回值,而这个过程中observavle的功能被定制化了,包括线程切换(需要在什么线程下来处理数据等)。直到递归的出口,最终决定数据在什么线程中被处理。最后回调到Consumer中的accept方法抛出处理后的数据。仔细思考一下,这种方式设计确实很优雅,创建observable与observer是初期就确定的,通过订阅操作subscribe将两者建立起关系。数据发射一层层包装到最下游,当“知道”下游需要什么的样数据、需要在什么线程处理,在一层层由下至上拿到每一层的定制化需要最终返回结果。所以做了这么多次的线程切换表明上看只有第一次成功了、実際には内部的にSubscribeTaskを介した連続的な切り替えの最終結果であり、他の切り替えが「有効になっていない」ということではありません。