Utilizzare i casi per gli scheduler RxJava


253

In RxJava ci sono 5 diversi programmatori tra cui scegliere:

  1. immediate () : crea e restituisce uno Scheduler che esegue immediatamente il lavoro sul thread corrente.

  2. trampolino () : crea e restituisce uno Scheduler che le code lavorano sul thread corrente per essere eseguite dopo il completamento del lavoro corrente.

  3. newThread () : crea e restituisce uno Scheduler che crea un nuovo thread per ogni unità di lavoro.

  4. computation () : crea e restituisce uno Scheduler destinato al lavoro computazionale. Questo può essere usato per loop di eventi, elaborazione di callback e altri lavori computazionali. Non eseguire lavori associati a IO su questo scheduler. Usa programmatori. io () invece.

  5. io () : crea e restituisce uno Scheduler destinato al lavoro associato a IO. L'implementazione è supportata da un pool di thread Executor che crescerà secondo necessità. Questo può essere usato per eseguire IO di blocco in modo asincrono. Non eseguire lavori di calcolo su questo scheduler. Usa programmatori. computazione () invece.

Domande:

I primi 3 programmatori sono piuttosto autoesplicativi; tuttavia, sono un po 'confuso riguardo al calcolo e io .

  1. Che cosa è esattamente il "lavoro con IO"? Viene utilizzato per gestire flussi ( java.io) e file ( java.nio.files)? Viene utilizzato per le query del database? Viene utilizzato per il download di file o l'accesso alle API REST?
  2. In che modo il calcolo () è diverso da newThread () ? Tutte le chiamate di calcolo () si trovano ogni volta su un singolo thread (in background) anziché su un nuovo thread (in background)?
  3. Perché è male chiamare il calcolo () quando si fa il lavoro IO?
  4. Perché è male chiamare io () quando si fa un lavoro computazionale?

Risposte:


332

Grandi domande, penso che la documentazione potrebbe fare con qualche dettaglio in più.

  1. io()è supportato da un pool di thread illimitato ed è il tipo di cose che useresti per attività non impegnative dal punto di vista computazionale, ovvero cose che non caricano molto sulla CPU. Quindi l'interazione con il file system, l'interazione con database o servizi su un host diverso sono buoni esempi.
  2. computation()è supportato da un pool di thread limitato con dimensioni pari al numero di processori disponibili. Se hai provato a pianificare il lavoro intensivo della CPU in parallelo su più processori disponibili (diciamo utilizzando newThread()), allora sei pronto per l'overhead di creazione del thread e l'overhead del cambio di contesto mentre i thread si contendono un processore ed è potenzialmente un grande successo in termini di prestazioni.
  3. È meglio partire solo computation()per il lavoro intensivo della CPU, altrimenti non otterrai un buon utilizzo della CPU.
  4. È male richiedere io()un lavoro computazionale per il motivo discusso in 2. io()non ha limiti e se si pianificano io()in parallelo un migliaio di attività computazionali, ognuna di quelle migliaia avrà il proprio thread e sarà in competizione per la CPU che incorrerà in costi di cambio di contesto.

5
Attraverso la familiarità con la fonte RxJava. Per molto tempo è stata fonte di confusione e penso che la documentazione debba essere rafforzata in questo senso.
Dave Moten,

2
@IgorGanapolsky Immagino che sia qualcosa che raramente vorresti fare. La creazione di un nuovo thread per ogni unità di lavoro raramente favorisce l'efficienza poiché i thread sono costosi da costruire e demolire. In genere si desidera riutilizzare i thread utilizzati da computation () e altri programmatori. L'unica volta che newThread () potrebbe avere un uso legittimo (almeno mi viene in mente) è dare il via a compiti isolati, rari e di lunga durata. Anche allora potrei usare io () per quello scenario.
tmn

4
Potresti mostrare un esempio in cui il trampolino () sarebbe utile? Capisco il concetto ma non riesco a capire uno scenario che userei in pratica. È l'unico programmatore che è ancora un mistero per me
tmn

32
Per le chiamate di rete utilizzare Schedulers.io () e se è necessario limitare il numero di chiamate di rete simultanee utilizzare Scheduler.from (Executors.newFixedThreadPool (n)).
Dave Moten,

4
Potresti pensare che mettere timeoutdi default su di computation()te significherebbe bloccare un thread, ma non è così. Sotto le coperte computation()utilizza un ScheduledExecutorServicetempo così le azioni ritardate non si bloccano. Dato questo fatto computation()è una buona idea perché se fosse su un altro thread, saremmo soggetti ai costi di cambio thread.
Dave Moten,

3

Il punto più importante è che sia Schedulers.io che Schedulers.computation sono supportati da pool di thread illimitati rispetto agli altri citati nella domanda. Questa caratteristica è condivisa solo da Schedulers.from (Executor) nel caso in cui Executor venga creato con newCachedThreadPool (non associato a un pool di thread a recupero automatico).

Come ampiamente spiegato nelle risposte precedenti e in più articoli sul Web, Schedulers.io e Schedulers.computation devono essere usati con attenzione in quanto ottimizzati per il tipo di lavoro a loro nome. Ma, dal mio punto di vista, il loro ruolo più importante è quello di fornire una reale concorrenza ai flussi reattivi .

Contrariamente alla convinzione dei nuovi arrivati, i flussi reattivi non sono intrinsecamente concorrenti ma intrinsecamente asincroni e sequenziali. Per questo motivo, Schedulers.io deve essere utilizzato solo quando l'operazione di I / O sta bloccando (ad esempio: l'utilizzo di un comando di blocco come Apache IOUtils FileUtils.readFileAsString (...) ) congelerebbe quindi il thread chiamante fino a quando l'operazione non viene eseguita fatto.

L'uso di un metodo asincrono come Java AsynchronousFileChannel (...) non bloccherebbe il thread chiamante durante l'operazione, quindi non ha senso utilizzare un thread separato. In effetti, i thread Schedulers.io non sono davvero adatti per le operazioni asincrone in quanto non eseguono un ciclo di eventi e il callback non sarebbe mai ... chiamato.

La stessa logica si applica all'accesso al database o alle chiamate API remote. Non utilizzare Schedulers.io se è possibile utilizzare un'API asincrona o reattiva per effettuare la chiamata.

Torna alla concorrenza. Potresti non avere accesso a un'API asincrona o reattiva per eseguire operazioni di I / O in modo asincrono o simultaneo, quindi l'unica alternativa è inviare più chiamate su un thread separato. Purtroppo, i flussi reattivi sono sequenziali alle loro estremità, ma la buona notizia è che l' operatore flatMap () può introdurre la concorrenza al loro interno .

La concorrenza deve essere creata nel costrutto del flusso, in genere utilizzando l' operatore flatMap () . Questo potente operatore può essere configurato per fornire internamente un contesto multi-thread alla funzione integrata flatMap () <T, R>. Tale contesto è fornito da uno Scheduler multi-thread come Scheduler.io o Scheduler.computation .

Trova maggiori dettagli negli articoli su Scheduler e concorrenza RxJava2 dove troverai esempi di codice e spiegazioni dettagliate su come usare gli Scheduler in modo sequenziale e simultaneo.

Spero che questo ti aiuti,

Softjake


2

Questo post sul blog fornisce una risposta eccellente

Dal post del blog:

Schedulers.io () è supportato da un pool di thread illimitato. Viene utilizzato per operazioni di tipo I / O non impegnative per la CPU, tra cui l'interazione con il file system, l'esecuzione di chiamate di rete, interazioni con il database, ecc. Questo pool di thread è destinato a essere utilizzato per eseguire IO di blocco in modo asincrono.

Schedulers.computation () è supportato da un pool di thread limitato con dimensioni fino al numero di processori disponibili. Viene utilizzato per lavori computazionali o ad uso intensivo di CPU come il ridimensionamento delle immagini, l'elaborazione di grandi set di dati, ecc. Prestare attenzione: quando si allocano più thread di calcolo rispetto ai core disponibili, le prestazioni diminuiranno a causa del cambio di contesto e dell'overhead di creazione dei thread mentre i thread si spostano per tempo dei processori.

Schedulers.newThread () crea un nuovo thread per ogni unità di lavoro pianificata. Questo programmatore è costoso poiché ogni volta viene generato un nuovo thread e non si verifica alcun riutilizzo.

Schedulers.from (Executorecutor) crea e restituisce uno scheduler personalizzato supportato dall'esecutore specificato. Per limitare il numero di thread simultanei nel pool di thread, utilizzare Scheduler.from (Executors.newFixedThreadPool (n)). Ciò garantisce che se un'attività è pianificata quando tutti i thread sono occupati, verrà messo in coda. I thread nel pool esisteranno fino a quando non verranno esplicitamente arrestati.

Il thread principale o AndroidSchedulers.mainThread () è fornito dalla libreria di estensioni RxAndroid a RxJava. Il thread principale (noto anche come thread dell'interfaccia utente) è il punto in cui si verifica l'interazione dell'utente. Bisogna fare attenzione a non sovraccaricare questo thread per evitare un'interfaccia utente non reattiva o, peggio ancora, la finestra di dialogo Applicazione non rispondente "(ANR).

Schedulers.single () è una novità di RxJava 2. Questo scheduler è supportato da un singolo thread che esegue le attività in sequenza nell'ordine richiesto.

Schedulers.trampoline () esegue le attività in modo FIFO (First In, First Out) da uno dei thread di lavoro partecipanti. Viene spesso utilizzato durante l'implementazione della ricorsione per evitare di aumentare lo stack di chiamate.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.