RxJava 2中的并发

本文介绍了在RxJava 2中如何进行并发处理,包括调度器的使用、并发操作符如subscribeOn和observeOn的应用,以及如何利用flatMap实现并发。通过示例说明了并发的重要性,如何避免阻塞主线程,提高应用性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

多线程应用程序具有两个或多个可以并行运行的部分。 这使应用程序可以更好地利用设备CPU内部的内核。 这使它可以更快地完成任务,并为用户带来更流畅,更响应的体验。

用Java进行并发编码可能很RxJava ,但是由于RxJava ,它现在更容易实现。 使用RxJava ,您只需要声明(声明性地)要在其上执行任务的线程,而不是(必须)创建和管理线程。

RxJava利用Schedulers以及subscribeOn()observeOn()并发运算符来实现此目的。 在本教程中,您将了解SchedulerssubscribeOn()运算符, observeOn()运算符,以及如何利用flatMap()运算符来实现并发。 但是首先,让我们从RxJava Schedulers开始。

先决条件

要继续学习本教程,您应该熟悉以下内容:

RxJava 2中的调度程序

RxJava中的Schedulers用于在线程上执行工作单元。 Scheduler提供了对Android和Java线程机制的抽象。 当您要运行任务并利用Scheduler执行该任务时, Scheduler将转到其线程池(准备使用的线程集合),然后在可用线程中运行任务。

您还可以指定任务应在一个特定线程中运行。 (有两个运算符subscribeOn()observeOn() ,可用于指定应在Scheduler线程池中的哪个线程上执行任务。)

如您所知,在Android中,不应在主线程上运行长时间运行的进程或占用大量CPU的任务。 如果由一个申购ObserverObservable的主线程上进行,任何相关的运营商将在主线程也跑。 对于长时间运行的任务(例如,执行网络请求)或占用大量CPU的任务(例如,图像转换),这将阻塞UI,直到任务完成,从而导致可怕的ANR(应用程序无响应)对话框和应用程序崩溃。 这些操作符可以改为使用observeOn()操作符切换到另一个线程。

在下一节中,我们将探讨各种Schedulers及其用途。

调度程序的类型

这是RxJavaRxAndroid可用的一些Schedulers类型,用于指示执行任务的线程类型。

  • Schedulers.immediate() :返回一个Scheduler ,该Scheduler在当前线程中立即执行工作。 请注意,这将阻塞当前线程,因此应谨慎使用。
  • Schedulers.trampoline() :计划当前线程中的任务。 这些任务不会立即执行,而是在线程完成其当前任务之后执行。 这与Schedulers.immediate()不同,因为它不立即执行任务,而是等待当前任务完成。
  • Schedulers.newThread() :启动一个新线程,并为每个Observer返回一个Scheduler以在新线程中执行任务。 您应该小心使用它,因为新线程之后不会再使用,而是被销毁了。
  • Schedulers.computation() :这为我们提供了一个Scheduler ,旨在用于计算密集型工作,例如图像转换,复杂计算等。此操作完全使用CPU内核。 此Scheduler使用固定的线程池大小,该大小取决于CPU内核,以实现最佳用法。 您应注意不要创建比可用CPU内核更多的线程,因为这会降低性能。
  • Schedulers.io() :创建并返回一个为I / O绑定工作(例如执行异步网络调用或读写数据库)指定的Scheduler 。 这些任务不占用大量CPU,或者利用Schedulers.computation()
  • Schedulers.single() :创建并返回Scheduler并在单个线程中顺序执行多个任务。
  • Schedulers.from(Executor executor) :这将创建一个Scheduler ,它将在给定的Executor上执行任务或工作单元。
  • AndroidSchedulers.mainThread() :这将创建一个Scheduler ,该Scheduler在Android应用程序主线程上执行任务。 此调度程序类型由RxAndroid库提供。

subscribeOn()运算符

通过使用subscribeOn()并发运算符,您可以指定Scheduler应在Observable上游执行操作。 然后它将使用相同的线程将值推送到Observers 。 现在让我们看一个实际的例子:

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import io.reactivex.Observable;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;

public class MainActivity extends AppCompatActivity {

    private static final String[] STATES = { "Lagos", "Abuja", "Abia",
            "Edo", "Enugu", "Niger", "Anambra"};

    private Disposable mDisposable = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Observable<String> observable = Observable.create(dataSource())
                .subscribeOn(Schedulers.newThread())
                .doOnComplete(() -> Log.d("MainActivity", "Complete"));

        mDisposable = observable.subscribe(s -> {
            Log.d("MainActivity", "received " + s + " on thread " + Thread.currentThread().getName());
        });
    }

    private ObservableOnSubscribe<String> dataSource() {
       return(emitter -> {
            for(String state : STATES) {
                emitter.onNext(state);
                Log.d("MainActivity", "emitting " + state + " on thread " + Thread.currentThread().getName());
                Thread.sleep(600);
            }
            emitter.onComplete();
        });
    }

    @Override
    protected void onDestroy() {
        if (mDisposable != null && !mDisposable.isDisposed()) {
            mDisposable.dispose();
        }
        super.onDestroy();
    }
}

在上面的代码中,我们有一个静态ArrayList ,其中包含尼日利亚的一些州。 我们还有一个字段为Disposable 。 我们通过调用Observable.subscribe()获得Disposable实例,稍后在调用dispose()方法释放所有已使用的资源时将使用它。 这有助于防止内存泄漏。 我们的dataSource()方法(可以从远程或本地数据库源返回数据)将返回ObservableOnSubscribe<T> :这是我们稍后使用Observable.create()方法创建自己的Observable所必需的。

dataSource()方法内部,我们遍历数组,通过调用emitter.onNext()将每个元素发送给Observers 。 发出每个值之后,我们将线程Hibernate,以模拟正在执行的大量工作。 最后,我们调用onComplete()方法向Observers发出信号,告知我们已完成传递值,并且它们不应再期望了。

现在,我们的dataSource()方法不应在主UI线程上执行。 但是如何指定呢? 在上面的示例中,我们提供了Schedulers.newThread()作为subscribeOn()的参数。 这意味着dataSource()操作将在新线程中运行。 还要注意,在上面的示例中,我们只有一个Observer 。 如果我们有多个Observers ,则每个Observers都有自己的线程。

为了使我们能够看到此工作,我们的ObserverObservable打印出其在onNext()方法中获得的值。

当我们运行它并在Android Studio上查看日志时,您可以看到从dataSource()方法向Observer的发射发生在Observer收到它们的同一线程RxNewThreadScheduler-1

Android Studio logcat结果显示单个线程上的执行日志

如果不指定.subscribeOn()的方法后Observable.create()方法,它将当前线程,这在我们的情况是在主线程上执行,从而阻止该应用程序的用户界面。

Android Studio Logcat在主线程上显示执行日志

关于subscribeOn()运算符,您应该了解一些重要的细节。 您应该在Observable链中只有一个subscribeOn() ; 在链中的任何位置添加另一个将完全无效。 为了清楚起见,建议将此运算符放置在尽可能靠近源的位置。 换句话说,将其放置在操作员链中的第一位。

Observable.create(dataSource())
                .subscribeOn(Schedulers.computation()) // this has effect
                .subscribeOn(Schedulers.io()) // has no effect 
                .doOnNext(s -> {
                    saveToCache(s); // executed on Schedulers.computation()
                })

observeOn()运算符

如我们所见, subscribeOn()并发运算符将指示Observable使用哪个Scheduler将排放沿着Observable链推向Observers

另一方面, observeOn()并发运算符的工作是将后续的发射切换到另一个线程或Scheduler 。 我们使用此运算符来控制下游消费者将在哪些线程上接收排放。 让我们看一个实际的例子。

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.TextView;
import io.reactivex.Observable;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;

public class ObserveOnActivity extends AppCompatActivity {

    private Disposable mDisposable = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TextView textView = (TextView) findViewById(R.id.tv_main);

        Observable<String> observable = Observable.create(dataSource())
                .subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .doOnComplete(() -> Log.d("ObserveOnActivity", "Complete"));

        mDisposable = observable.subscribe(s -> {
           Log.d("ObserveOnActivity", "received " + s + " on thread " + Thread.currentThread().getName());
            textView.setText(s);
        });
    }

    private ObservableOnSubscribe<String> dataSource() {
        return(emitter -> {
            Thread.sleep(800);
            emitter.onNext("Value");
            Log.d("ObserveOnActivity", "dataSource() on thread " + Thread.currentThread().getName());
            emitter.onComplete();
        });
    }
// ... 
}

在上面的代码中,我们使用了observeOn()运算符,然后将AndroidSchedulers.mainThread()传递给了它。 我们要做的是将线程从Schedulers.newThread()切换到Android主线程。 这是必需的,因为我们要更新TextView小部件,并且只能从主UI线程执行此操作。 请注意,如果您在尝试更新TextView小部件时未切换到主线程,则该应用程序将崩溃并引发CalledFromWrongThreadException

不同于subscribeOn()运算符, observeOn()操作者可以在操作员链施加多次,从而改变Scheduler一次以上。

Observable<String> observable = Observable.create(dataSource())
                .subscribeOn(Schedulers.newThread())
                .observeOn(Schedulers.io())
                .doOnNext(s -> {
                    saveToCache(s);
                    Log.d("ObserveOnActivity", "doOnNext() on thread " + Thread.currentThread().getName());
                })
                .observeOn(AndroidSchedulers.mainThread())
                .doOnComplete(() -> Log.d("ObserveOnActivity", "Complete"));

该代码具有两个observeOn()运算符。 第一个使用Schedulers.io() ,这意味着saveToCache()方法将在Schedulers.io()线程上执行。 之后,它会切换到AndroidSchedulers.mainThread()Observers将从那里接收上游的排放。

Android Studio logcat结果显示日志

与flatMap()运算符并发

flatMap()运算符是另一个非常强大且重要的运算符,可用于实现并发。 根据官方文档的定义如下:

将可观察对象发出的项目转换为可观察项目,然后将这些项目的排放展平为单个可观察项目。

FlatMap运算符图

让我们看一个使用此运算符的实际示例:

//...
    @Override
    protected void onCreate(Bundle savedInstanceState) { 
        // ...
        final String[] states = {"Lagos", "Abuja", "Imo", "Enugu"};
        Observable<String> statesObservable = Observable.fromArray(states);
        
        statesObservable.flatMap(
                    s -> Observable.create(getPopulation(s))
          ).subscribe(pair -> Log.d("MainActivity", pair.first + " population is " + pair.second));
    }

    private ObservableOnSubscribe<Pair> getPopulation(String state) {
        return(emitter -> {
            Random r = new Random();
            Log.d("MainActivity", "getPopulation() for " + state + " called on " + Thread.currentThread().getName());
            emitter.onNext(new Pair(state, r.nextInt(300000 - 10000) + 10000));
            emitter.onComplete();
        });
    }
}

这将在Android Studio logcat上打印以下内容:

getPopulation() for Lagos called on main
Lagos population is 80362
getPopulation() for Abuja called on main
Abuja population is 132559
getPopulation() for Imo called on main
Imo population is 34106
getPopulation() for Enugu called on main
Enugu population is 220301

从上面的结果中,您可以看到我们得到的结果与数组中的顺序相同。 另外,每种状态的getPopulation()方法都是在同一线程(主线程)上处理的。 这会使输出结果变慢,因为它们是在主线程上顺序处理的。

现在,为了使我们能够与该运算符并发,我们希望每种状态的getPopulation()方法(来自statesObservable发射)在不同的线程上进行处理。 这样做将加快处理速度。 我们将使用flatMap()运算符执行此操作,因为它会为每个发射创建一个新的Observable 。 然后,我们将subscribeOn()并发运算符应用于每个并发运算符,并将Scheduler传递给它。

statesObservable.flatMap(
                s -> Observable.create(getPopulation(s))
                .subscribeOn(Schedulers.io())
        ).subscribe(pair -> Log.d("MainActivity", pair.first + " population is " + pair.second));

当每个发射产生一个ObservableflatMap()运算符的工作是将它们合并在一起,然后将它们作为单个流发送出去。

getPopulation() for Lagos called on RxCachedThreadScheduler-1
Lagos population is 143965
getPopulation() for Abuja called on RxCachedThreadScheduler-2
getPopulation() for Enugu called on RxCachedThreadScheduler-4
Abuja population is 158363
Enugu population is 271420
getPopulation() for Imo called on RxCachedThreadScheduler-3
Imo population is 81564

在上面的结果中,我们可以观察到每个状态的getPopulation()方法是在不同的线程上处理的。 这使处理速度更快,但同时也观察到Observer接收到的flatMap()运算符的发射与上游原始发射的顺序不同。

结论

在本教程中,您学习了如何使用RxJava 2处理并发性:它是什么,可用的不同Schedulers以及如何使用subscribeOn()observeOn()并发运算符。 我还向您展示了如何使用flatMap()运算符实现并发。

翻译自: https://code.tutsplus.com/tutorials/concurrency-in-rxjava-2--cms-29288

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值