web-dev-qa-db-fra.com

Cas d'utilisation pour les ordonnanceurs RxJava

Dans RxJava il y a 5 ordonnanceurs différents parmi lesquels choisir:

  1. immediate () : Crée et retourne un planificateur qui exécute immédiatement le travail sur le thread actuel.

  2. trampoline () : crée et retourne un planificateur dont les files d'attente fonctionnent sur le fil actuel à exécuter une fois le travail en cours terminé.

  3. newThread () : crée et retourne un planificateur qui crée un nouveau thread pour chaque unité de travail.

  4. computation () : Crée et renvoie un planificateur destiné au travail de calcul. Ceci peut être utilisé pour les boucles d'événement, le traitement des rappels et d'autres travaux de calcul. N'effectuez pas de travaux liés aux entrées-sorties sur ce planificateur. Utilisez des planificateurs. io () à la place.

  5. io () : crée et renvoie un planificateur destiné au travail lié à l'IO. L'implémentation est soutenue par un pool de threads de l'exécuteur qui augmentera au besoin. Ceci peut être utilisé pour effectuer des entrées/sorties bloquantes de manière asynchrone. N'effectuez pas de travail de calcul sur ce planificateur. Utilisez les planificateurs. computation () à la place.

Des questions:

Les 3 premiers ordonnanceurs sont assez explicites; Cependant, je suis un peu confus à propos de le calcul et io .

  1. Qu'est-ce que le "travail lié aux entrées-sorties"? Est-il utilisé pour traiter les flux (Java.io) et les fichiers (Java.nio.files)? Est-il utilisé pour les requêtes de base de données? Est-il utilisé pour télécharger des fichiers ou accéder à des API REST?
  2. En quoi le calcul () est-il différent de newThread () ? Est-ce que tous les appels computation () se font sur un seul thread (d'arrière-plan) au lieu d'un nouveau thread (d'arrière-plan) à chaque fois?
  3. Pourquoi est-il mauvais d'appeler computation () lorsque vous travaillez IO?
  4. Pourquoi est-il mauvais d'appeler io () lors d'un travail de calcul?
241
bcorso

Excellentes questions, je pense que la documentation pourrait faire avec plus de détails.

  1. io() est soutenu par un pool de threads non limité et est le type de chose que vous utiliseriez pour les tâches ne nécessitant pas de calcul intensif, c'est-à-dire des tâches qui ne sollicitent pas beaucoup le processeur. Ainsi, l’interaction avec le système de fichiers, l’interaction avec des bases de données ou des services sur un hôte différent en sont de bons exemples.
  2. computation() est soutenu par un pool de threads limité dont la taille est égale au nombre de processeurs disponibles. Si vous avez essayé de planifier en parallèle des travaux gourmands en ressources processeur sur plusieurs processeurs disponibles (par exemple, en utilisant newThread()), vous êtes prêt pour la surcharge de création de threads et la commutation de contexte lorsque les threads se disputent un processeur. .
  3. Il est préférable de laisser computation() uniquement pour un travail intensif en CPU, sinon vous n'obtiendrez pas une bonne utilisation de celui-ci.
  4. Il est mauvais d'appeler io() pour le travail de calcul pour la raison décrite à l'étape 2. io() est illimité et si vous planifiez un millier de tâches de calcul sur io() en parallèle, chacune de ces mille tâches avoir leur propre thread et être en compétition pour les coûts de commutation de contexte engendrés par la CPU.
317
Dave Moten

Le point le plus important est que Schedulers.io et Schedulers.computation sont sauvegardés par des pools de threads sans limite, contrairement aux autres dans la question. Cette caractéristique est uniquement partagée par Schedulers.from (Executor) dans le cas où Executor est créé avec newCachedThreadPool (illimité avec un pool de threads de récupération automatique).

Comme abondamment expliqué dans les réponses précédentes et dans de nombreux articles sur le Web, Schedulers.io et Schedulers.computation doivent être utilisés avec précaution car ils sont optimisés pour le type de travail en leur nom. Mais, à mon point de vue, leur rôle le plus important est de fournir une concurrence réelle aux flux réactifs .

Contrairement à la croyance des nouveaux arrivants, les flux réactifs ne sont pas intrinsèquement concurrents mais intrinsèquement asynchrones et séquentiels. Pour cette raison même, Schedulers.io ne doit être utilisé que lorsque l'opération d'E/S est bloquante (par exemple: utilisation d'une commande de blocage telle que Apache IOUtils FileUtils .readFileAsString (...) ) gèlera donc le thread appelant jusqu'à ce que l'opération soit terminée.

L'utilisation d'une méthode asynchrone telle que Java AsynchronousFileChannel (...) ne bloque pas le thread appelant pendant l'opération, il est donc inutile d'utiliser un thread séparé. En fait, les threads Schedulers.io ne conviennent pas vraiment aux opérations asynchrones, car ils n'exécutent pas de boucle d'événement et le rappel ne serait jamais ... appelé.

La même logique s'applique pour l'accès à la base de données ou les appels d'API distants. N'utilisez pas Schedulers.io si vous pouvez utiliser une API asynchrone ou réactive pour effectuer l'appel.

Retour à la concurrence. Il se peut que vous n'ayez pas accès à une API asynchrone ou réactive pour effectuer des opérations d'E/S de manière asynchrone ou simultanée. Votre seule alternative est donc de distribuer plusieurs appels sur un thread séparé. Hélas, Les flux réactifs sont séquentiels à leurs extrémités mais la bonne nouvelle est que la flatMap () l'opérateur peut introduire la concurrence sur son noyau .

La simultanéité doit être construite dans la construction de flux, généralement à l'aide de l'opérateur flatMap () . Cet opérateur puissant peut être configuré pour fournir en interne un contexte multi-thread à votre flatMap () , fonction intégrée <T, R>. Ce contexte est fourni par un planificateur multithread tel que Scheduler.io ou Scheduler.computation .

Trouvez plus de détails dans les articles sur RxJava2 Schedulers et Concurrency où vous trouverez des exemples de code et des explications détaillées sur la façon d'utiliser les planificateurs de manière séquentielle et simultanée.

J'espère que cela t'aides,

Softjake

2
softjake

Ce blog fournit une excellente réponse

De l'article de blog:

Schedulers.io () est soutenu par un pool de threads non lié. Il est utilisé pour les travaux de type E/S peu gourmands en ressources processeur, notamment les interactions avec le système de fichiers, les appels réseau, les interactions de base de données, etc.

Schedulers.computation () est sauvegardé par un pool de threads lié dont la taille peut atteindre le nombre de processeurs disponibles. Il est utilisé pour des tâches de calcul ou de calcul intensives telles que le redimensionnement des images, le traitement de grands ensembles de données, etc. temps des transformateurs.

Schedulers.newThread () crée un nouveau thread pour chaque unité de travail planifiée. Ce planificateur est coûteux car un nouveau thread est généré à chaque fois et aucune réutilisation ne se produit.

Schedulers.from (Executor executor) crée et renvoie un planificateur personnalisé sauvegardé par l'exécuteur spécifié. Pour limiter le nombre de threads simultanés dans le pool de threads, utilisez Scheduler.from (Executors.newFixedThreadPool (n)). Cela garantit que si une tâche est planifiée alors que tous les threads sont occupés, elle sera mise en file d'attente. Les threads du pool existeront jusqu'à ce qu'il soit explicitement arrêté.

Le fil principal ou AndroidSchedulers.mainThread () est fourni par la bibliothèque d’extensions RxAndroid vers RxJava. Le fil principal (également appelé fil de l'interface utilisateur) est l'endroit où se déroule l'interaction de l'utilisateur. Veillez à ne pas surcharger ce thread pour éviter l’interface utilisateur non réactif avec janky ou, pire, la boîte de dialogue ANR (Application Not Responding).

Schedulers.single () est une nouveauté de RxJava 2. Ce planificateur est sauvegardé par un seul thread exécutant les tâches de manière séquentielle dans l'ordre demandé.

Schedulers.trampoline () exécute les tâches de manière FIFO (premier entré, premier sorti) par l'un des threads de travail participants. Il est souvent utilisé lors de la mise en œuvre de la récursivité pour éviter de développer la pile d’appels.

1
joe