c# .net task-parallel-library system.reactive

c# - TPL vs Reactive Framework



.net task-parallel-library (6)

¿Cuándo elegiríamos usar Rx sobre TPL o los 2 marcos son ortogonales?

Por lo que entiendo, Rx está destinado principalmente a proporcionar una abstracción sobre los eventos y permitir la composición, pero también permite proporcionar una abstracción sobre las operaciones asincrónicas. utilizando las sobrecargas Createxx y las sobrecargas y cancelaciones de Fromxxx mediante la eliminación del IDisposable devuelto.

TPL también proporciona una abstracción para las operaciones a través de Tareas y habilidades de cancelación.

Mi dilema es cuándo usar qué y para qué escenarios.


Algunas pautas que me gusta seguir:

  • ¿Estoy lidiando con datos que no origino? ¿Datos que llegan cuando les da la gana? Entonces RX.
  • ¿Estoy originando cálculos y necesito administrar concurrencia? Entonces TPL.
  • ¿Estoy administrando múltiples resultados y necesito elegirlos según el tiempo? Entonces RX.

El objetivo principal de Rx no es proporcionar una abstracción sobre los eventos. Este es solo uno de sus resultados. Su objetivo principal es proporcionar un modelo de empuje composable para colecciones.

El marco reactivo (Rx) se basa en que IObservable<T> es el dual matemático de IEnumerable<T> . Entonces, en lugar de elementos de "extracción" de una colección usando IEnumerable<T> , podemos tener objetos "empujados" a través de IObservable<T> .

Por supuesto, cuando realmente buscamos fuentes observables, cosas como eventos y operaciones asincrónicas son excelentes candidatos.

El marco reactivo, naturalmente, requiere un modelo de subprocesos múltiples para poder ver las fuentes de datos observables y para administrar consultas y suscripciones. Rx hace un uso intensivo del TPL para hacer esto.

Entonces, si usas Rx estás usando implícitamente el TPL.

Utilizaría el TPL directamente si desea un control directo sobre sus tareas.

Pero si tiene fuentes de datos que desea observar y realizar consultas en contra, recomiendo totalmente el marco reactivo.


Hice una aplicación para Windows Phone. Comenzó con RX. El problema fue que en algún momento utilicé una nueva versión de RX y ¿adivinen qué? Muchas funciones obtuvieron el atributo "obsoleto". Entonces comencé con TPL y ahora tengo un proyecto mixto con ambos. Mi consejo es que si utilizas muchas llamadas web asíncronas, es mejor usar TPL. Como ya se ha escrito, RX está usando TPL, ¿por qué no usar TPL desde el principio?


Me gustan las viñetas de Scott W. Para poner algunos ejemplos más concretos en los mapas Rx muy bien para

  • consumiendo arroyos
  • realizar trabajos asincrónicos sin bloqueo como solicitudes web.
  • eventos de transmisión (eventos .net como movimiento del mouse o eventos de tipo de mensaje de bus de servicio)
  • Componer "secuencias" de eventos juntos
  • Operaciones de estilo Linq
  • Exponer flujos de datos de su API pública

TPL parece hacer un buen mapa de

  • paralelización interna del trabajo
  • realizar trabajos asincrónicos sin bloqueo como solicitudes web
  • realizando flujos de trabajo y continuaciones

Una cosa que he notado con IObservable (Rx) es que se vuelve omnipresente. Una vez que esté en su base de código, ya que sin duda estará expuesto a través de otras interfaces, finalmente aparecerá en toda su aplicación. Me imagino que esto puede ser aterrador al principio, pero la mayoría del equipo se siente bastante cómodo con Rx ahora y adora la cantidad de trabajo que nos ahorra.

IMHO Rx será la biblioteca dominante sobre el TPL, ya que es compatible con .NET 3.5, 4.0, Silverlight 3, Silverlight 4 y Javascript. Esto significa que efectivamente debe aprender un estilo y es aplicable a muchas plataformas.

EDITAR : He cambiado de opinión sobre que Rx es dominante sobre TPL. Resuelven diferentes problemas, por lo que no deberían ser comparados de esa manera. Con .NET 4.5 / C # 5.0 las palabras clave async / await nos vincularán aún más al TPL (lo cual es bueno). Para un debate profundo sobre Rx vs eventos vs TPL, etc. revise el primer capítulo de mi libro en línea IntroToRx.com


Yo diría que TPL Dataflow cubre un subconjunto especializado de funcionalidad en Rx. El flujo de datos es para el procesamiento de datos, que puede tomar una cantidad medible de tiempo, mientras que Rx es para eventos, como la posición del mouse, estados de error, etc. donde el tiempo de manipulación es insignificante.

Ejemplo: su controlador "suscribir" es asíncrono y no desea más de 1 ejecutor en ese momento. Con Rx tienes que bloquear, no hay otra forma de evitarlo, porque Rx es asíncrona y no amenaza asincrónica de manera especial en muchos lugares.

.Subscribe(myAsyncHandler().Result)

Si no bloquea, Rx considerará que la acción está completa mientras el controlador todavía se está ejecutando de forma asíncrona.

Podrías pensar que si lo haces

.ObserveOn(Scheduler.EventLoopSchedule)

de que el problema se resuelva Pero esto romperá su flujo de trabajo .Complete (), porque Rx pensará que se realizará tan pronto como programe la ejecución y saldrá de la aplicación sin esperar a que se complete la operación asincrónica.

Si no desea permitir más de 4 tareas asincrónicas simultáneas, Rx no ofrece nada de inmediato. Quizás puedas hackear algo implementando tu propio programador, buffer, etc.

TPL Dataflow ofrece una solución muy buena en ActionBlock. Puede limitar las acciones simultáneas a cierto número y comprende las operaciones asincrónicas, de modo que al llamar a Complete () ya la espera de Completed hará exactamente lo que cabría esperar: esperar a que se completen todas las tareas asincrónicas en progreso.

Otra característica que TPL tiene es "contrapresión". Digamos que descubrió un error en su rutina de manejo y necesita recalcular los datos del mes pasado. Si se suscribe a su fuente utilizando Rx, y su canalización contiene búferes ilimitados u ObserveOn, se le agotará la memoria en cuestión de segundos porque la fuente seguirá leyendo más rápido de lo que el procesamiento puede manejar. Incluso si implementa el bloqueo de los consumidores, su fuente puede sufrir bloqueos de llamadas, por ejemplo, si la fuente es asincrónica. En TPL puede implementar la fuente como

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

que no bloquea la fuente aún estará esperando mientras el controlador está sobrecargado.

En general, encontré que Rx es adecuado para acciones que son lentas y computacionalmente livianas. Si el tiempo de procesamiento se vuelve sustancial, se encuentra en el mundo de los extraños efectos secundarios y la depuración esotérica.

La buena noticia es que los bloques TPL Dataflow funcionan muy bien con Rx. Tienen adaptadores AsObserver / AsObservable y puede colocarlos en el medio de la tubería Rx cuando sea necesario. Pero Rx tiene muchos más patrones y casos de uso. Así que mi regla de oro es comenzar con Rx y agregar TPL Dataflow según sea necesario.


Actualización, diciembre de 2016: si tiene 30 minutos, le recomiendo que lea la cuenta de primera mano de Joe Duffy en lugar de mi especulación. Creo que mi análisis se mantiene bien, pero si ha encontrado esta pregunta, le recomiendo que vea la publicación del blog en lugar de estas respuestas porque, además de TPL vs Rx.NET, también cubre proyectos de investigación de MS (Midori, Cosmos).

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

Creo que MS cometió un gran error al corregir en exceso después de que salió .NET 2.0. Introdujeron muchas API de administración de concurrencia diferentes, todas al mismo tiempo, desde diferentes partes de la empresa.

  • Steven Toub estaba presionando mucho para primitivas seguras para subprocesos para reemplazar Event (que comenzó como Future<T> y se convirtió en Task<T> )
  • MS Research tenía MIN-LINQ y extensiones reactivas (Rx)
  • Hardware / Embedded tenía tiempo de ejecución de robótica (CCR)

Mientras tanto, muchos equipos de API administrados intentaban vivir con APM y Threadpool.QueueUserWorkItem() , sin saber si Toub ganaría su batalla para enviar Future<T> / Task<T> en mscorlib.dll. Al final parece que cubrieron y enviaron tanto Task<T> como IObservable<T> en mscorlib, pero no permitieron ninguna otra Rx API (ni siquiera ISubject<T> ) en mscorlib. Creo que este seto terminó causando una gran cantidad de duplicaciones (más adelante) y un esfuerzo desperdiciado dentro y fuera de la empresa.

Para la duplicación, vea: Task vs. IObservable<Unit> , Task<T> vs. AsyncSubject<T> , Task.Run() vs. Observable.Start() . Y esto es solo la punta del iceberg. Pero en un nivel superior, considere:

  • StreamInsight: secuencias de eventos SQL, código nativo optimizado, pero consultas de eventos definidas mediante la sintaxis LINQ
  • TPL Dataflow: construido en TPL, construido en paralelo a Rx, optimizado para ajustar el paralelismo de subprocesos, no es bueno para redactar consultas
  • Rx: increíble expresividad, pero cargada de peligros. IEnumerable streams ''calientes'' con métodos de extensión estilo IEnumerable , lo que significa que bloqueas muy fácilmente para siempre (llamar nunca a First() en una transmisión caliente). Los límites de programación (limitación del paralelismo) se realizan a través de métodos de extensión de SubscribeOn() bastante extraños, que son extrañamente implícitos y difíciles de entender. Si comienza a aprender Rx reserve un largo tiempo para aprender todos los peligros que debe evitar. Pero Rx es realmente la única opción para componer secuencias de eventos complejos o necesita un filtrado / consulta complejos.

No creo que Rx tenga una oportunidad de luchar en una amplia adopción hasta que MS envíe ISubject<T> en mscorlib. Lo cual es triste, porque Rx contiene algunos tipos concretos (genéricos) muy útiles, como TimeInterval<T> y Timestamped<T> , que creo que deberían estar en Core / mscorlib como Nullable<T> . Además, System.Reactive.EventPattern<TEventArgs> .