Casos de uso para planejadores RxJava

253

No RxJava, existem 5 agendadores diferentes para você escolher:

  1. imediato () : cria e retorna um agendador que executa o trabalho imediatamente no segmento atual.

  2. trampolim () : cria e retorna um agendador que enfileira o trabalho no encadeamento atual a ser executado após a conclusão do trabalho atual.

  3. newThread () : cria e retorna um agendador que cria um novo thread para cada unidade de trabalho.

  4. computation () : cria e retorna um agendador destinado ao trabalho computacional. Isso pode ser usado para loops de eventos, processamento de retornos de chamada e outros trabalhos computacionais. Não execute trabalho vinculado a E / S neste planejador. Use agendadores. io () em vez disso.

  5. io () : cria e retorna um agendador destinado ao trabalho vinculado a E / S. A implementação é apoiada por um pool de threads do Executor que aumentará conforme necessário. Isso pode ser usado para executar de forma assíncrona o bloqueio de E / S. Não execute trabalho computacional neste planejador. Use agendadores. computation () .

Questões:

Os três primeiros agendadores são bastante auto-explicativos; no entanto, estou um pouco confuso sobre computação e io .

  1. O que exatamente é "trabalho vinculado a IO"? É usado para lidar com fluxos ( java.io) e arquivos ( java.nio.files)? É usado para consultas ao banco de dados? É usado para baixar arquivos ou acessar APIs REST?
  2. Qual a diferença entre computação () e newThread () ? Será que todas as chamadas de computação () estão em um único segmento (segundo plano) em vez de um novo segmento (segundo plano) de cada vez?
  3. Por que é ruim chamar computation () ao fazer um trabalho de IO?
  4. Por que é ruim chamar io () ao fazer um trabalho computacional?
bcorso
fonte

Respostas:

332

Ótimas perguntas, acho que a documentação poderia ter mais detalhes.

  1. io()é apoiado por um pool de encadeamentos ilimitado e é o tipo de coisa que você usaria para tarefas que não são computacionalmente intensivas, ou seja, coisas que não colocam muita carga na CPU. Portanto, sim, a interação com o sistema de arquivos, a interação com bancos de dados ou serviços em um host diferente são bons exemplos.
  2. computation()é apoiado por um conjunto de encadeamentos limitado com tamanho igual ao número de processadores disponíveis. Se você tentou agendar um trabalho intensivo da CPU em paralelo em mais do que os processadores disponíveis (por exemplo, usando newThread()), você estará preparado para a sobrecarga de criação de threads e a sobrecarga de alternância de contexto, à medida que os threads disputam um processador e isso é potencialmente um grande impacto no desempenho.
  3. É melhor sair computation() o trabalho intensivo da CPU apenas, caso contrário você não obterá uma boa utilização da CPU.
  4. É ruim exigir io()trabalho computacional pelo motivo discutido em 2. io()é ilimitado e se você programar mil tarefas computacionais io()paralelamente, cada uma dessas mil tarefas terá seu próprio encadeamento e competirá pelos custos de troca de contexto incorridos pela CPU.
Dave Moten
fonte
5
Pela familiaridade com a fonte RxJava. Foi uma fonte de confusão para mim por um longo tempo e acho que a documentação precisa ser aprimorada a esse respeito.
Dave Moten
2
@IgorGanapolsky Acho que é algo que você raramente gostaria de fazer. Criar um novo encadeamento para cada unidade de trabalho raramente é propício à eficiência, já que os encadeamentos são caros de serem construídos e desmontados. Você normalmente deseja reutilizar threads que computation () e outros agendadores fazem. A única vez que newThread () pode ter uso legítimo (pelo menos eu consigo pensar) é iniciar tarefas isoladas, pouco frequentes e de longa duração. Mesmo assim, eu poderia usar io () para esse cenário.
tmn
4
Você poderia mostrar um exemplo em que o trampolim () seria útil? Entendo o conceito, mas não consigo descobrir um cenário que o usaria na prática. É o único programador que id ainda um mistério para mim
tmn
32
Para chamadas de rede, use Schedulers.io () e, se você precisar limitar o número de chamadas de rede simultâneas, use Scheduler.from (Executors.newFixedThreadPool (n)).
Dave Moten
4
Você pode pensar que colocar timeoutpor padrão computation()estaria bloqueando um thread, mas não é o caso. Sob as cobertas computation()usa um ScheduledExecutorServicetempo para que ações atrasadas não sejam bloqueadas. Dado esse fato, computation()é uma boa idéia, porque se estivesse em outro encadeamento, estaríamos sujeitos a custos de troca de encadeamentos.
precisa
3

O ponto mais importante é que aputação de Schedulers.io e Schedulers.com é suportada por pools de encadeamentos ilimitados, em oposição aos outros mencionados na pergunta. Essa característica é compartilhada apenas pelos Schedulers.from (Executor) no caso em que o Executor é criado com newCachedThreadPool (ilimitado com um pool de threads de recuperação automática).

Conforme explicado em abundância nas respostas anteriores e em vários artigos na Web, aputação Schedulers.io e Schedulers.com deve ser usada com cuidado, pois são otimizados para o tipo de trabalho em seu nome. Mas, do meu ponto de vista, o papel mais importante é fornecer simultaneidade real para fluxos reativos .

Contrariamente à crença dos recém-chegados, os fluxos reativos não são inerentemente simultâneos, mas inerentemente assíncronos e seqüenciais. Por esse motivo, Schedulers.io deve ser usado apenas quando a operação de E / S estiver bloqueando (por exemplo: usando um comando de bloqueio como o Apache IOUtils FileUtils.readFileAsString (...) ) congelaria o encadeamento de chamada até que a operação seja feito.

O uso de um método assíncrono, como Java AsynchronousFileChannel (...), não bloqueia o encadeamento de chamada durante a operação, portanto, não faz sentido usar um encadeamento separado. De fato, Schedulers.io encadeamentos não são realmente adequados para operações assíncronas, pois não executam um loop de eventos e o retorno de chamada nunca ... seria chamado.

A mesma lógica se aplica ao acesso ao banco de dados ou às chamadas remotas à API. Não use o Schedulers.io se você puder usar uma API assíncrona ou reativa para fazer a chamada.

Voltar para simultaneidade. Você pode não ter acesso a uma API assíncrona ou reativa para executar operações de E / S de forma assíncrona ou simultânea, portanto, sua única alternativa é enviar várias chamadas em um encadeamento separado. Infelizmente, os fluxos reativos são seqüenciais em seus fins, mas a boa notícia é que o operador flatMap () pode introduzir simultaneidade em seu núcleo .

A simultaneidade deve ser construída na construção de fluxo, normalmente usando o operador flatMap () . Este poderoso operador pode ser configurado para fornecer internamente um contexto multithread para sua função incorporada flatMap () <T, R>. Esse contexto é fornecido por um agendador multiencadeado, como Scheduler.io ou Scheduler.computation .

Encontre mais detalhes em artigos sobre agendadores RxJava2 e Concorrência , onde você encontrará amostra de código e explicações detalhadas sobre como usar Schedulers sequencialmente e simultaneamente.

Espero que isto ajude,

Softjake

softjake
fonte
2

Esta postagem do blog fornece uma excelente resposta

Na postagem do blog:

Schedulers.io () é apoiado por um pool de encadeamentos ilimitado. É usado para trabalhos de tipo de E / S que não consomem muita CPU, incluindo interação com o sistema de arquivos, realização de chamadas de rede, interações de banco de dados, etc.

Schedulers.computation () é apoiado por um conjunto de encadeamentos limitado com tamanho até o número de processadores disponíveis. É usado para trabalhos computacionais ou intensivos em CPU, como redimensionar imagens, processar grandes conjuntos de dados, etc. Cuidado: quando você aloca mais encadeamentos de computação que os núcleos disponíveis, o desempenho diminui devido à alternância de contexto e sobrecarga de criação de encadeamento à medida que os encadeamentos disputam tempo dos processadores.

Schedulers.newThread () cria um novo thread para cada unidade de trabalho agendada. Esse agendador é caro, pois o novo encadeamento é gerado toda vez e não ocorre reutilização.

Schedulers.from (executor do executor) cria e retorna um agendador personalizado suportado pelo executor especificado. Para limitar o número de threads simultâneos no pool de threads, use Scheduler.from (Executors.newFixedThreadPool (n)). Isso garante que, se uma tarefa for agendada quando todos os threads estiverem ocupados, ela será colocada na fila. Os encadeamentos no pool existirão até que seja explicitamente encerrado.

Thread principal ou AndroidSchedulers.mainThread () é fornecido pela biblioteca de extensões RxAndroid para RxJava. O thread principal (também conhecido como thread da interface do usuário) é onde a interação do usuário acontece. Deve-se tomar cuidado para não sobrecarregar esse encadeamento para impedir a interface do usuário instável e sem resposta ou, pior, a caixa de diálogo Application Not Responding ”(ANR).

Schedulers.single () é novo no RxJava 2. Esse agendador é apoiado por um único encadeamento que executa tarefas sequencialmente na ordem solicitada.

Schedulers.trampoline () executa tarefas de maneira FIFO (Primeira entrada, Primeira saída) por um dos segmentos de trabalho participantes. É frequentemente usado na implementação da recursão para evitar o aumento da pilha de chamadas.

Joe
fonte