Pergunta

Quando alguém escolheria usar Rx em vez de TPL ou as duas estruturas são ortogonais?

Pelo que entendi, o Rx tem como objetivo principal fornecer uma abstração sobre eventos e permitir a composição, mas também permite fornecer uma abstração sobre operações assíncronas.usando as sobrecargas Createxx e as sobrecargas Fromxxx e cancelamento por meio do descarte do IDisposable retornado.

O TPL também fornece uma abstração para operações por meio de tarefas e recursos de cancelamento.

Meu dilema é quando usar qual e para quais cenários?

Foi útil?

Solução

O principal objetivo do Rx não é fornecer uma abstração sobre eventos.Este é apenas um dos seus resultados.Seu objetivo principal é fornecer um modelo push combinável para coleções.

A estrutura reativa (Rx) é baseada em IObservable<T> sendo o dual matemático de IEnumerable<T>.Então, em vez de "puxar" itens de uma coleção usando IEnumerable<T> podemos ter objetos "empurrados" para nós via IObservable<T>.

É claro que, quando procuramos fontes observáveis, coisas como eventos e operações assíncronas são excelentes candidatos.

A estrutura reativa requer naturalmente um modelo multithread para poder observar as fontes de dados observáveis ​​e gerenciar consultas e assinaturas.Na verdade, Rx faz uso intenso do TPL para fazer isso.

Portanto, se você usar Rx, estará implicitamente usando o TPL.

Você usaria o TPL diretamente se desejasse controle direto sobre suas tarefas.

Mas se você tiver fontes de dados que deseja observar e realizar consultas, recomendo totalmente a estrutura reativa.

Outras dicas

Algumas diretrizes que gosto de seguir:

  • Estou lidando com dados que não origino. Dados que chegam quando quiser? Então rx.
  • Estou originando cálculos e preciso gerenciar a simultaneidade? Então tpl.
  • Estou gerenciando vários resultados e preciso escolher entre eles com base no prazo? Então rx.

Eu gosto dos pontos de bala de Scott W. Para colocar alguns exemplos mais concretos em mapas RX realmente bem para

  • consumindo fluxos
  • Executar o trabalho assíncrono não bloqueador como solicitações da Web.
  • Eventos de streaming (eventos .NET, como movimento de movimento do mouse ou eventos do tipo de mensagem de barramento de serviço)
  • Compondo "fluxos" de eventos juntos
  • Operações de estilo LINQ
  • Expondo fluxos de dados da sua API pública

TPL parece mapear bem para

  • paralelalização interna do trabalho
  • executar o trabalho assíncrono sem bloqueio, como solicitações da web
  • realizando fluxos de trabalho e continuações

Uma coisa que notei com o IOBServable (RX) é que ele se torna difundido. Uma vez na sua base de código, como sem dúvida será exposto por outras interfaces, ele acabará aparecendo em todo o seu aplicativo. Eu imagino que isso pode ser assustador no começo, mas a maior parte da equipe está bastante confortável com o RX agora e adora a quantidade de trabalho que nos salva.

O IMHO RX será a biblioteca dominante sobre o TPL, pois já é suportado no .NET 3.5, 4.0, Silverlight 3, Silverlight 4 e JavaScript. Isso significa que você efetivamente precisa aprender um estilo e é aplicável a muitas plataformas.

EDITAR: Eu mudei de idéia sobre o RX ser dominante sobre o TPL. Eles resolvem problemas diferentes, portanto não devem ser comparados assim. Com o .NET 4.5/C# 5.0, as palavras -chave assíncronas/aguardam nos amarrarão ainda mais ao TPL (o que é bom). Para um discuson profundo em rx vs eventos vs tpl etc. confira o Primeiro capítulo do meu livro online INTROTORX.COM

Atualização, dezembro de 2016: Se você tiver 30 minutos, recomendo que você leia a conta em primeira mão de Joe Duffy, em vez da minha especulação. Acho que minha análise se mantém bem, mas se você encontrou essa pergunta, recomendo que você veja a postagem do blog em vez dessas respostas, porque além do TPL vs RX.NET, ele também abrange projetos de pesquisa de MS (Midori, Cosmos).

http://joeduffyblog.com/2016/11/30/15 anos-of-concurrency/


Eu acho que a MS cometeu um grande erro correto demais depois que o .NET 2.0 foi lançado. Eles introduziram muitas APIs diferentes de gerenciamento de simultaneidade, todas ao mesmo tempo de diferentes partes da empresa.

  • Steven Toub estava se esforçando para que os primitivos seguros para fios substituam o evento (que começou como Future<T> e se transformou em Task<T>)
  • A pesquisa da EM teve extensões Min-Linq e Reativo (RX)
  • Hardware/incorporado teve Robótica Cuntime (CCR)

Enquanto isso, muitas equipes de API gerenciadas estavam tentando viver com APM e Threadpool.QueueUserWorkItem(), sem saber se Toub venceria sua luta para enviar Future<T>/Task<T> em mscorlib.dll. No final, parece que eles heduziram e enviados os dois Task<T> e IObservable<T> em Mscorlib, mas não permitiu outras APIs RX (nem mesmo ISubject<T>) em Mscorlib. Eu acho que esse hedge acabou causando uma enorme quantidade de duplicação (mais tarde) e desperdiçou o esforço dentro e fora da empresa.

Para duplicação, consulte: Task vs. IObservable<Unit>, Task<T> vs. AsyncSubject<T>, Task.Run() vs. Observable.Start(). E isso é só o topo do iceberg. Mas em um nível mais alto considere:

  • StreamInsight-fluxos de eventos SQL, otimizados nativos, mas consultas de eventos definidas usando sintaxe LINQ
  • TPL Dataflow - construído no TPL, construído em paralelo ao RX, otimizado para ajustar o paralelismo de encadeamento, não é bom em compor consultas
  • RX - Expressividade incrível, mas repleta de perigo. Fluxos 'quentes' com mixes com IEnumerableMétodos de extensão do estilo, o que significa que você bloqueia com muita facilidade para sempre (chamando First() em um fluxo quente nunca retorna). Os limites de agendamento (limitando o paralelismo) são feitos por meio de um pouco estranho SubscribeOn() Métodos de extensão, que são estranhamente implícitos e difíceis de acertar. Se estiver começando a aprender RX reserve muito tempo para aprender todas as armadilhas a serem evitadas. Mas o RX é realmente a única opção se compor fluxos de eventos complexos ou você precisar de filtragem/consulta complexa.

Eu não acho ISubject<T> em Mscorlib. O que é triste, porque o RX contém alguns tipos de concreto (genéricos) muito úteis, como TimeInterval<T> e Timestamped<T>, que eu acho que deveria estar no núcleo/mscorlib como Nullable<T>. Também, System.Reactive.EventPattern<TEventArgs>.

Eu diria que o TPL Dataflow aborda o subconjunto de funcionalidade especializado no RX. O DataFlow é para processamento de dados, que pode levar uma quantidade mensurável de tempo, enquanto o RX é para eventos, como posição do mouse, estados de erro, etc., onde o tempo de manuseio é insignificante.

Exemplo: seu manipulador de "inscrição" é assíncrono e você não quer mais de 1 executor na época. Com o RX que você precisa bloquear, não há outra maneira de contornar, porque o RX é assíncrono-agnóstico e não ameaça assíncrono de uma maneira especial em muitos lugares.

.Subscribe(myAsyncHandler().Result)

Se você não bloquear, o RX considerará que a ação será concluída enquanto o manipulador ainda está sendo executado de forma assíncrona.

Você pode pensar que se você fizer

.ObserveOn(Scheduler.EventLoopSchedule)

do que o problema é resolvido. Mas isso quebrará seu fluxo de trabalho .complete (), porque o RX pensará que ele será feito assim que agendar a execução e você deixará seu aplicativo sem aguardar a conclusão da operação assíncrona.

Se você deseja permitir não mais de 4 tarefas simultâneas assíncronas, o RX não oferece nada fora da caixa. Talvez você possa invadir algo implementando seu próprio agendador, buffer etc.

O TPL Dataflow oferece uma solução muito boa no ActionBlock. Ele pode acelerar ações simultâneas para determinado número e entende as operações assíncronas; portanto, chamar complete () e aguardar para concluir fará exatamente o que você esperaria: aguardando todas as tarefas assíncronas em andamento.

Outro recurso que o TPL tem é "Backpressure". Digamos que você tenha descoberto um erro na sua rotina de manuseio e precisa recalcá -lo no mês passado. Se você se inscrever na sua fonte usando o RX e seu pipeline contiver buffers ilimitados ou observar, você ficará sem memória em questão de segundos, porque a fonte continuará lendo mais rápido do que o processamento pode lidar. Mesmo se você implementar o bloqueio de consumidor, sua fonte poderá sofrer chamadas de bloqueio, por exemplo, se a fonte for assíncrona. Em TPL, você pode implementar a fonte como

while(...)
    await actionBlock.SendAsync(msg)

que ainda não bloqueia a fonte aguardará enquanto o manipulador está sobrecarregado.

No geral, descobri que o RX é bom para ações que são tempo e luz computacionalmente. Se o tempo de processamento se tornar substancial, você está no mundo dos efeitos colaterais estranhos e da depuração esotérica.

A boa notícia é que os blocos de fluxo de dados TPL são muito bons com o RX. Eles têm adaptadores ASOBSERVER/ASOBSERVABLE e você pode colocá -los no meio do pipeline RX quando necessário. Mas o RX tem muito mais padrões e casos de uso. Portanto, minha regra geral é começar com o RX e adicionar TPL Dataflow conforme necessário.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top