Actualmente me estoy familiarizando con el marco de extensiones reactivas para .NET y estoy trabajando a través de los diversos recursos de introducción que he encontrado (principalmente http://www.introtorx.com )
Nuestra aplicación involucra una serie de interfaces de hardware que detectan marcos de red, estos serán mis IObservables, luego tendré una variedad de componentes que consumirán esos marcos o realizarán alguna forma de transformación en los datos y producirán un nuevo tipo de marco. También habrá otros componentes que deberán mostrar cada enésimo fotograma, por ejemplo. Estoy convencido de que Rx será útil para nuestra aplicación, sin embargo, estoy luchando con los detalles de implementación de la interfaz IObserver.
La mayoría (si no todos) de los recursos que he estado leyendo han dicho que no debería implementar la interfaz IObservable yo mismo, sino usar una de las funciones o clases proporcionadas. De mi investigación, parece que la creación de un Subject<IBaseFrame>
me proporcionaría lo que necesito, tendría mi único hilo que lee datos de la interfaz de hardware y luego llama a la función OnNext de mi Subject<IBaseFrame>
instancia. Los diferentes componentes de IObserver recibirían sus notificaciones de ese sujeto.
Mi confusión proviene del consejo que se da en el apéndice de este tutorial, donde dice:
Evite el uso de tipos de sujetos. Rx es efectivamente un paradigma de programación funcional. Usar sujetos significa que ahora estamos administrando el estado, que potencialmente está mutando. Tratar tanto con el estado mutante como con la programación asincrónica al mismo tiempo es muy difícil de hacer bien. Además, muchos de los operadores (métodos de extensión) se han escrito cuidadosamente para garantizar que se mantenga una vida útil correcta y coherente de las suscripciones y secuencias; cuando presenta los temas, puede romper esto. Las versiones futuras también pueden ver una degradación significativa del rendimiento si utiliza sujetos explícitamente.
Mi aplicación es bastante crítica para el rendimiento, obviamente voy a probar el rendimiento del uso de patrones Rx antes de que entre en el código de producción; sin embargo, me preocupa que estoy haciendo algo que va en contra del espíritu del marco Rx al usar la clase Asunto y que una versión futura del marco va a dañar el rendimiento.
¿Existe una mejor manera de hacer lo que quiero? El subproceso de sondeo de hardware se ejecutará continuamente, ya sea que haya observadores o no (el búfer de HW se respaldará de lo contrario), por lo que esta es una secuencia muy activa. Luego, necesito pasar los fotogramas recibidos a varios observadores.
Cualquier consejo será muy apreciado.
fuente
Respuestas:
Ok, si ignoramos mis formas dogmáticas e ignoramos "los temas son buenos / malos" todos juntos. Miremos el espacio del problema.
Apuesto a que tienes 1 de 2 estilos de sistema en los que necesitas integrarte.
Para la opción 1, fácil, simplemente lo envolvemos con el método FromEvent apropiado y listo. ¡Al bar!
Para la opción 2, ahora debemos considerar cómo sondear esto y cómo hacerlo de manera eficiente. Además, cuando obtenemos el valor, ¿cómo lo publicamos?
Me imagino que querrías un hilo dedicado para las encuestas. No querrás que otro codificador golpee ThreadPool / TaskPool y te deje en una situación de inanición de ThreadPool. Alternativamente, no quiere la molestia de cambiar de contexto (supongo). Así que supongamos que tenemos nuestro propio hilo, probablemente tendremos algún tipo de ciclo While / Sleep en el que nos sentaremos para sondear. Cuando el cheque encuentra algunos mensajes los publicamos. Bueno, todo esto suena perfecto para Observable.Create. Ahora probablemente no podamos usar un bucle While, ya que eso no nos permitirá devolver un Desechable para permitir la cancelación. Afortunadamente, has leído todo el libro, ¡así que estás familiarizado con la programación recursiva!
Imagino que algo como esto podría funcionar. #No probado
La razón por la que realmente no me gustan los temas es que, por lo general, el desarrollador no tiene un diseño claro sobre el problema. Hackear un tema, pincharlo aquí y allá, y luego dejar que el pobre desarrollador de soporte adivine que WTF estaba sucediendo. Cuando utiliza los métodos Crear / Generar, etc., está localizando los efectos en la secuencia. Puede verlo todo en un método y sabe que nadie más está produciendo un efecto secundario desagradable. Si veo los campos de una materia, ahora tengo que buscar todos los lugares de una clase en la que se está utilizando. Si algún MFer expone uno públicamente, entonces todas las apuestas están canceladas, ¡quién sabe cómo se está utilizando esta secuencia! Async / Concurrency / Rx es difícil. No necesita hacerlo más difícil permitiendo que los efectos secundarios y la programación de causalidad le hagan girar la cabeza aún más.
fuente
En general, debes evitar usarlos
Subject
, sin embargo, para lo que estás haciendo aquí, creo que funcionan bastante bien. Hice una pregunta similar cuando encontré el mensaje "evitar temas" en los tutoriales de Rx.Para citar a Dave Sexton (de Rxx)
Tiendo a usarlos como punto de entrada a Rx. Entonces, si tengo algún código que necesita decir 'algo sucedió' (como tú), usaría un
Subject
y llamarOnNext
. Luego, exponga esoIObservable
para que otros se suscriban (puede usarloAsObservable()
en su tema para asegurarse de que nadie pueda enviar a un Asunto y estropear las cosas).También podría lograr esto con un evento y uso .NET
FromEventPattern
, pero si solo voy a convertir el evento en un evento deIObservable
todos modos, no veo el beneficio de tener un evento en lugar de unSubject
(lo que podría significar que me estoy perdiendo algo aqui)Sin embargo, lo que debe evitar con bastante fuerza es suscribirse a un
IObservable
con aSubject
, es decir, no pasar unSubject
alIObservable.Subscribe
método.fuente
A menudo, cuando administra un Asunto, en realidad solo está reimplementando funciones que ya están en Rx, y probablemente no de una manera tan sólida, simple y extensible.
Cuando intenta adaptar algún flujo de datos asincrónico a Rx (o crear un flujo de datos asincrónico a partir de uno que actualmente no es asincrónico), los casos más comunes suelen ser:
La fuente de datos es un evento : como dice Lee, este es el caso más simple: use FromEvent y diríjase al pub.
La fuente de datos proviene de una operación sincrónica y desea actualizaciones sondeadas (por ejemplo, un servicio web o una llamada a la base de datos): en este caso, podría usar el enfoque sugerido por Lee, o para casos simples, podría usar algo como
Observable.Interval.Select(_ => <db fetch>)
. Es posible que desee utilizar DistinctUntilChanged () para evitar la publicación de actualizaciones cuando nada ha cambiado en los datos de origen.La fuente de datos es algún tipo de API asincrónica que llama a su devolución de llamada : en este caso, use Observable.Create para conectar su devolución de llamada para llamar a OnNext / OnError / OnComplete en el observador.
La fuente de datos es una llamada que se bloquea hasta que haya nuevos datos disponibles (por ejemplo, algunas operaciones de lectura de socket síncronas): en este caso, puede usar Observable.Create para envolver el código imperativo que lee desde el socket y lo publica en Observer.OnNext cuando se leen los datos. Esto puede ser similar a lo que está haciendo con el sujeto.
Usar Observable.Create frente a crear una clase que gestiona un sujeto es bastante equivalente a usar la palabra clave yield frente a crear una clase completa que implemente IEnumerator. Por supuesto, puede escribir un IEnumerator para que sea tan limpio y tan bueno como el código de rendimiento, pero ¿cuál está mejor encapsulado y tiene un diseño más ordenado? Lo mismo ocurre con Observable.Create frente a la gestión de sujetos.
Observable.Create le brinda un patrón limpio para una configuración perezosa y un desmontaje limpio. ¿Cómo se logra esto con una clase que envuelve una asignatura? Necesita algún tipo de método de inicio ... ¿cómo sabe cuándo llamarlo? ¿O simplemente lo inicia siempre, incluso cuando nadie está escuchando? Y cuando haya terminado, ¿cómo puede hacer que deje de leer desde el socket / sondear la base de datos, etc.? Debe tener algún tipo de método de detención, y aún debe tener acceso no solo al IObservable al que está suscrito, sino a la clase que creó el Asunto en primer lugar.
Con Observable.Create, todo está reunido en un solo lugar. El cuerpo de Observable.Create no se ejecuta hasta que alguien se suscribe, por lo que si nadie se suscribe, nunca usas tu recurso. Y Observable.Create devuelve un Desechable que puede cerrar limpiamente sus recursos / devoluciones de llamada, etc., esto se llama cuando el Observador cancela la suscripción. La vida útil de los recursos que está utilizando para generar el Observable está estrechamente relacionada con la vida útil del Observable en sí.
fuente
El texto del bloque entre comillas explica en gran medida por qué no debería usarlo
Subject<T>
, pero para ponerlo más simple, está combinando las funciones de observador y observable, mientras inyecta algún tipo de estado en el medio (ya sea que esté encapsulando o extendiendo).Aquí es donde se mete en problemas; estas responsabilidades deben estar separadas y ser distintas entre sí.
Dicho esto, en su caso específico , le recomiendo que divida sus inquietudes en partes más pequeñas.
Primero, tiene su hilo que está activo y siempre monitorea el hardware en busca de señales para generar notificaciones. ¿Cómo harías esto normalmente? Eventos . Así que comencemos con eso.
Definamos el
EventArgs
que disparará su evento.Ahora, la clase que disparará el evento. Tenga en cuenta que esto podría ser una clase estática (ya que siempre tiene un hilo en ejecución monitoreando el búfer de hardware), o algo que llame a pedido que se suscriba a eso . Tendrá que modificar esto según corresponda.
Entonces ahora tienes una clase que expone un evento. Los observables funcionan bien con eventos. Tanto es así que existe un soporte de primera clase para convertir flujos de eventos (piense en un flujo de eventos como múltiples disparos de un evento) en
IObservable<T>
implementaciones si sigue el patrón de evento estándar, a través del método estáticoFromEventPattern
en laObservable
clase .Con la fuente de tus eventos y el
FromEventPattern
método, podemos crearIObservable<EventPattern<BaseFrameEventArgs>>
fácilmente (laEventPattern<TEventArgs>
clase incorpora lo que verías en un evento .NET, en particular, una instancia derivadaEventArgs
y un objeto que representa al remitente), así:Por supuesto, quieres una
IObservable<IBaseFrame>
, pero eso es fácil, usando elSelect
método de extensión en laObservable
clase para crear una proyección (como lo harías en LINQ, y podemos resumir todo esto en un método fácil de usar):fuente
IObservable<T>
como sin información sobre cómo Actualmente se está señalizando con esa información.Es malo generalizar que los sujetos no son buenos para usar en una interfaz pública. Si bien es cierto que esta no es la forma en que debería verse un enfoque de programación reactiva, definitivamente es una buena opción de mejora / refactorización para su código clásico.
Si tiene una propiedad normal con un descriptor de acceso de conjunto público y desea notificar los cambios, no hay nada en contra de reemplazarla con un BehaviorSubject. El INPC u otros eventos adicionales simplemente no son tan limpios y personalmente me desgasta. Para este propósito, puede y debe usar BehaviorSubjects como propiedades públicas en lugar de propiedades normales y deshacerse de INPC u otros eventos.
Además, la interfaz de sujeto hace que los usuarios de su interfaz sean más conscientes de la funcionalidad de sus propiedades y es más probable que se suscriban en lugar de simplemente obtener el valor.
Es lo mejor para usar si desea que otros escuchen / se suscriban a los cambios de una propiedad.
fuente