web-dev-qa-db-fra.com

RxJava Observation sur le fil d'appel / d'abonnement

J'ai du mal à comprendre comment subscribeOn/observeOn fonctionne dans RxJava. J'ai créé une application simple avec observable qui émet les noms des planètes du système solaire, effectue une cartographie et un filtrage et imprime les résultats.

Si je comprends bien, la planification du travail sur le thread d'arrière-plan se fait via l'opérateur subscribeOn (et cela semble bien fonctionner).

L'observation sur le thread d'arrière-plan fonctionne également très bien avec l'opérateur observeOn.

Mais j'ai du mal à comprendre comment observer le thread appelant (que ce soit le thread principal ou tout autre). Cela se fait facilement sur Android avec l'opérateur AndroidSchedulers.mainThread(), mais je ne sais pas comment y parvenir en Java pur.

Voici mon code:

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() + ")";
    }
}

Observable dans créé et le travail est souscrit sur l'un des trois threads de l'exécuteur testamentaire. Cela fonctionne comme prévu. Mais comment observer les résultats sur ces threads créés dynamiquement dans la boucle for? Existe-t-il un moyen de créer Scheduler à partir du thread actuel?

De plus, j'ai découvert qu'après avoir exécuté ce code, il ne se termine jamais et je ne sais pas pourquoi? :(

39
Filip Zymek

Pour répondre à votre question, permettez-moi de commencer par le début, cela permet aux autres de comprendre ce que vous savez déjà.

Planificateurs

Les planificateurs jouent le même rôle que les exécuteurs pour Java. En bref - ils décident quelles actions de thread sont exécutées.

Habituellement, un observable et les opérateurs s'exécutent dans le thread actuel. Parfois, vous pouvez passer Scheduler à Observable ou à l'opérateur en tant que paramètre (par exemple, Observable.timer ()).

De plus, RxJava fournit 2 opérateurs pour spécifier le planificateur:

  • subscribeOn - spécifiez le planificateur sur lequel un observable fonctionnera
  • observeOn - spécifiez le planificateur sur lequel un observateur observera cet observable

Pour les comprendre rapidement, j'utilise un exemple de code:

Sur tous les exemples, j'utiliserai l'aide createObservable, qui émet un nom de thread sur lequel l'Observable opère:

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

Sans programmateurs:

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

S'abonnerSur:

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

S'abonner et observer:

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

Réponse:

AndroidSchedulers.mainThread () renvoie un sheduler qui délègue le travail à MessageQueue associé au thread principal.
À cette fin, il utilise Android.os.Looper.getMainLooper () et Android.os.Handler.

En d'autres termes, si vous souhaitez spécifier un thread particulier, vous devez fournir des moyens pour planifier et effectuer des tâches sur le thread.

En dessous, il peut utiliser n'importe quel type de MQ pour stocker des tâches et une logique qui boucle la Quee et exécute des tâches.

En Java, nous avons Executor qui est désigné pour de telles tâches. RxJava peut facilement créer un planificateur à partir d'un tel exécuteur.

Voici un exemple qui montre comment vous pouvez observer sur le fil principal (pas particulièrement utile mais montrer toutes les pièces requises).

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);
    }
}

Et la dernière question, pourquoi votre code ne se termine pas:

ThreadPoolExecutor utilise des threads non démons par défaut, donc votre programme ne se termine pas tant qu'ils n'existent pas. Vous devez utiliser la méthode shutdown pour fermer les threads.

80
Marek Hawrylczak

Voici un exemple simplifié mis à jour pour RxJava 2. C'est le même concept que la réponse de Marek: un exécuteur qui ajoute les exécutables à une BlockingQueue qui est consommée sur le thread de l'appelant.

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