2010-08-04 26 views
11

Tengo una aplicación multiproceso que genera hilos para varios instrumentos de hardware. Cada hilo es básicamente un bucle infinito (durante la vida útil de la aplicación) que sondea el hardware para nuevos datos y activa un evento (que pasa los datos) cada vez que recoge algo nuevo. Hay una única clase de oyente que consolida todos estos instrumentos, realiza algunos cálculos y desencadena un nuevo evento con este cálculo.Eventos vs. Rendimiento

Sin embargo, me pregunto si, dado que hay un solo oyente, sería mejor exponer un método IEnumerable<> de estos instrumentos, y usar un yield return para devolver los datos, en lugar de disparar los eventos.

Me gustaría ver si alguien sabe de las diferencias en estos dos métodos. En particular, estoy buscando la mejor confiabilidad, la mejor capacidad para pausar/cancelar operación, mejor para enhebrar, seguridad general, etc.

Además, con el segundo método es posible ejecutar el lazo IEnumerable en un hilo separado? Muchos de estos instrumentos están vinculados a la CPU, por lo que es vital asegurarse de que cada uno tenga un hilo diferente.

Respuesta

6

Esto suena como un muy buen caso de uso para las extensiones reactivas. Hay una pequeña curva de aprendizaje, pero en pocas palabras, IObservable es el doble de IEnumerable. Donde IEnumerable requiere que extraigas de él, IObservable envía sus valores al observador. Prácticamente en cualquier momento que necesite bloquear en su enumerador, es una buena señal que debe invertir el patrón y usar un modelo push. Los eventos son una forma de ir, pero IObservable tiene mucha más flexibilidad ya que es composable y sensible a los subprocesos.

instrument.DataEvents 
      .Where(x => x.SomeProperty == something) 
      .BufferWithTime(TimeSpan.FromSeconds(1)) 
      .Subscribe(x => DoSomethingWith(x)); 

En el ejemplo anterior, DoSomethingWith (x) se llamará siempre que el sujeto (instrumento) produce una DataEvent que tiene un SomeProperty a juego y que amortigua la eventos en lotes de 1 segundo de duración.

Hay mucho más que podría hacer, como fusionarse en los eventos producidos por otros temas o dirigir las notificaciones al hilo de la interfaz de usuario, etc.Lamentablemente, la documentación actualmente es bastante débil, pero hay buena información en Matthew Podwysocki's blog. (Aunque sus publicaciones mencionan casi exclusivamente Reactive Extensions para JavaScript, también se puede aplicar a Reactive Extensions para .NET)

+0

He usado Rx en un nuevo proyecto, y es maravilloso. El problema en este caso es que requerirá una revisión general del código heredado, ya que cambia la forma en que se aborda el problema. Para la próxima versión principal, ya estoy trabajando en esto, pero parece una tarea desalentadora para un pequeño (o) rediseño. – drharris

4

Es una decisión estrecha, pero creo que me quedaré con el modelo de eventos en este caso, con el principal motivo de que los futuros programadores de mantenimiento tengan menos probabilidades de comprender el concepto de rendimiento. Además, el rendimiento significa que el código que procesa cada solicitud de hardware está en el mismo hilo que el código que genera las solicitudes de procesamiento. Eso es malo, porque podría significar que su hardware tiene que esperar al código del consumidor.

Y hablando de consumidores, otra opción es una cola productor/consumidor. Sus instrumentos pueden presionar en la misma cola y su único oyente puede hacer lo mismo desde allí.

+0

No me preocupa demasiado el mantenimiento. Dado que esta es la parte de misión crítica de la aplicación, está tan documentada que los no programadores pueden encontrar su camino. Gracias por responder la pregunta de subprocesos. Y, buen puntero sobre el uso de una cola. Estoy viendo la nueva clase 'ConcurrentQueue ' y puede terminar yendo por esta ruta. – drharris

1

Stephen Toub blogs sobre una cola de bloqueo que implementa IEnumerable como un bucle infinito utilizando la palabra clave yield. Sus subprocesos de trabajo podrían poner en cola nuevos puntos de datos a medida que aparecen y el subproceso de cálculo podría quitarlos de la secuencia mediante un bucle foreach con semántica de bloqueo.

2

Hay una diferencia bastante fundamental, empuje contra tirón. El modelo de extracción (rendimiento) es el más difícil de implementar desde la vista de interfaz del instrumento. Porque tendrás que almacenar datos hasta que el código del cliente esté listo para tirar. Cuando presiona, el cliente puede o no almacenar, según lo considere necesario.

Pero la mayoría de las implementaciones prácticas en escenarios de subprocesos múltiples tienen que ocuparse de la sobrecarga en el inevitable cambio de contexto de subprocesos que se requiere para presentar los datos. Y eso a menudo se hace con pull, usando una cola acotada de seguridad de subprocesos.

0

No creo que haya mucha diferencia en cuanto al rendimiento entre el evento y el enfoque yield. Yield se evalúa perezosamente, por lo que deja una oportunidad para indicar a los hilos de producción que se detengan. Si su código está cuidadosamente documentado, el mantenimiento también debería ser un lavado.

Mi preferencia es una tercera opción, utilizar un método de devolución de llamada en lugar de un evento (aunque ambos implican delegados). Sus productores invocan la devolución de llamada cada vez que tienen datos. Las rellamadas pueden devolver valores, por lo que su consumidor puede indicar a los productores que paren o que continúen cada vez que ingresan datos.

Este enfoque puede darle lugares para optimizar el rendimiento si tiene un gran volumen de datos. En su devolución de llamada, bloquea un objeto neutral y anexa datos entrantes a una colección. El tiempo de ejecución internamente utiliza una cola lista en el objeto de bloqueo, por lo que puede servir como punto de cola.

Esto le permite elegir una colección, como List<T> con capacidad predefinida, que es O (1) para anexar. También puede duplicar el búfer de su consumidor, con la devolución de llamada adjuntando al búfer "izquierdo" mientras se consolida desde el "correcto", y así sucesivamente. Esto minimiza la cantidad de bloqueo del productor y los datos perdidos asociados, lo cual es útil para datos en ráfagas. También puede medir fácilmente las marcas de agua alta y las tasas de procesamiento a medida que varía la cantidad de hilos.