web-dev-qa-db-ja.com

RxJavaのスレッドの呼び出し/サブスクライブの観察

RxJavaでsubscribeOn/observeOnがどのように機能するかを理解するのに苦労しています。太陽系の惑星名を放出し、マッピングとフィルタリングを行い、結果を出力する、observableを使用したシンプルなアプリを作成しました。

私が理解しているように、バックグラウンドスレッドへの作業のスケジューリングはsubscribeOn演算子を介して行われます(そして、それはうまくいくようです)。

バックグラウンドスレッドでの監視も、observeOn演算子で正常に機能します。

しかし、スレッドの呼び出しを監視する方法(メインスレッドか他のスレッドか)を理解するのに苦労しています。 AndroidでAndroidSchedulers.mainThread()演算子を使用して簡単に実行できますが、Pure Javaでこれを実現する方法がわかりません。

私のコードは次のとおりです。

public class Main {

    public static void main(String[] args) throws InterruptedException {

        ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 5, 3000L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());

        System.out.println("Main thread: " + getCurrentThreadInfo());

        Observable<String> stringObservable = Observable.from(Arrays.asList("Merkury", "Wenus", "Ziemia", "Mars", "Jowisz", "Saturn", "Uran", "Neptun", "Pluton"))
                .map(in -> {
                    System.out.println("map on: " + getCurrentThreadInfo());
                    return in.toUpperCase();
                })
                .filter(in -> {
                    System.out.println("filter on: " + getCurrentThreadInfo());
                    return in.contains("A");
                })
                .subscribeOn(Schedulers.from(executor));

        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread("Thread-" + i) {
                @Override
                public void run() {
                    stringObservable
                            .buffer(5)
                            .subscribe(s -> System.out.println("Result " + s + " on: " + getCurrentThreadInfo()));
                }
            };
            thread.start();
        }

    }

    private static String getCurrentThreadInfo() {
        return Thread.currentThread().getName() + "(" + Thread.currentThread().getId() + ")";
    }
}

作成および作業で観察可能なものは、executorからの3つのスレッドの1つでサブスクライブされます。これは期待どおりに機能します。しかし、forループで動的に作成されたスレッドの結果をどのように観察するのでしょうか?現在のスレッドからスケジューラを作成する方法はありますか?

また、このコードを実行した後、コードが終了することはなく、理由もわかりません。 :(

39
Filip Zymek

あなたの質問に答えるために、最初から始めましょう。これにより、他の人があなたがすでに知っていることを理解できます。

スケジューラー

スケジューラーは、Executors for Javaと同じ役割を果たします。簡単に説明すると、実行するスレッドアクションを決定します。

通常、Observableおよびoperatorは現在のスレッドで実行されます。スケジューラーをパラメーターとしてObservableまたはoperatorに渡すことができます(例:Observable.timer())。

さらに、RxJavaには、スケジューラを指定する2つの演算子があります。

  • subscribeOn-Observableが動作するスケジューラを指定します
  • observeOn-オブザーバーがこのObservableを監視するスケジューラーを指定します

それらをすばやく理解するために、サンプルコードを使用します。

すべてのサンプルで、Observableが動作するスレッドの名前を発行するヘルパーcreateObservableを使用します。

 public static Observable<String> createObservable(){
        return Observable.create((Subscriber<? super String> subscriber) -> {
                subscriber.onNext(Thread.currentThread().getName());
                subscriber.onCompleted();
            }
        );
    }

スケジューラーなし:

createObservable().subscribe(message -> {
        System.out.println("Case 1 Observable thread " + message);
        System.out.println("Case 1 Observer thread " + Thread.currentThread().getName());
    });
    //will print:
    //Case 1 Observable thread main
    //Case 1 Observer thread main

購読する:

createObservable()
            .subscribeOn(Schedulers.newThread())
            .subscribe(message -> {
                System.out.println("Case 2 Observable thread " + message);
                System.out.println("Case 2 Observer thread " + Thread.currentThread().getName());
            });
            //will print:
            //Case 2 Observable thread RxNewThreadScheduler-1
            //Case 2 Observer thread RxNewThreadScheduler-1

SubscribeOnおよびObserveOn:

reateObservable()
            .subscribeOn(Schedulers.newThread())
            .observeOn(Schedulers.newThread())
            .subscribe(message -> {
                System.out.println("Case 3 Observable thread " + message);
                System.out.println("Case 3 Observer thread " + Thread.currentThread().getName());
            });
            //will print:
            //Case 3 Observable thread RxNewThreadScheduler-2
            //Case 3 Observer thread RxNewThreadScheduler-1

ObserveOn:

createObservable()
            .observeOn(Schedulers.newThread())
            .subscribe(message -> {
                System.out.println("Case 4 Observable thread " + message);
                System.out.println("Case 4 Observer thread " + Thread.currentThread().getName());
            });
            //will print:
            //Case 4 Observable thread main
            //Case 4 Observer thread RxNewThreadScheduler-1

回答:

AndroidSchedulers.mainThread()は、メインスレッドに関連付けられたMessageQueueに作業を委任するシェデュラーを返します。
この目的のために、Android.os.Looper.getMainLooper()およびAndroid.os.Handlerを使用します。

つまり、特定のスレッドを指定する場合は、スレッドでタスクをスケジュールして実行する手段を提供する必要があります。

その下では、Queをループしてタスクを実行するタスクとロジックを保存するために、あらゆる種類のMQを使用できます。

Javaには、そのようなタスク用に指定されたExecutorがあります。 RxJavaは、そのようなExecutorからスケジューラを簡単に作成できます。

以下は、メインスレッドでの観察方法を示す例です(特に有用ではありませんが、必要なすべての部分を示しています)。

public class RunCurrentThread implements Executor {

    private BlockingQueue<Runnable> tasks = new LinkedBlockingQueue<>();

    public static void main(String[] args) throws InterruptedException {
        RunCurrentThread sample = new RunCurrentThread();
        sample.observerOnMain();
        sample.runLoop();
    }

    private void observerOnMain() {
        createObservable()
                .subscribeOn(Schedulers.newThread())
                .observeOn(Schedulers.from(this))
                .subscribe(message -> {
                    System.out.println("Observable thread " + message);
                    System.out.println("Observer thread " + Thread.currentThread().getName());
                });
        ;
    }

    public Observable<String> createObservable() {
        return Observable.create((Subscriber<? super String> subscriber) -> {
                    subscriber.onNext(Thread.currentThread().getName());
                    subscriber.onCompleted();
                }
        );
    }

    private void runLoop() throws InterruptedException {
        while(!Thread.interrupted()){
            tasks.take().run();
        }
    }

    @Override
    public void execute(Runnable command) {
        tasks.add(command);
    }
}

そして最後の質問、コードが終了しない理由:

ThreadPoolExecutorはデフォルトで非デーモンスレッドを使用するため、プログラムは存在するまで終了しません。 shutdown メソッドを使用してスレッドを閉じる必要があります。

80

RxJava 2用に更新された簡単な例を次に示します。これは、マレクの答えと同じ概念です。呼び出し側のスレッドで消費されているBlockingQueueに実行可能ファイルを追加するエグゼキューターです。

public class ThreadTest {

    @Test
    public void test() throws InterruptedException {

        final BlockingQueue<Runnable> tasks = new LinkedBlockingQueue<>();

        System.out.println("Caller thread: " + Thread.currentThread().getName());

        Observable.fromCallable(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                System.out.println("Observable thread: " + Thread.currentThread().getName());
                return 1;
            }
        })
            .subscribeOn(Schedulers.io())
            .observeOn(Schedulers.from(new Executor() {
                @Override
                public void execute(@NonNull Runnable runnable) {
                    tasks.add(runnable);
                }
            }))
            .subscribe(new Consumer<Integer>() {
                @Override
                public void accept(@NonNull Integer integer) throws Exception {
                    System.out.println("Observer thread: " + Thread.currentThread().getName());
                }
            });
        tasks.take().run();
    }

}

// Output: 
// Caller thread main
// Observable thread RxCachedThreadScheduler-1
// Observer thread main
7
miguel