2011-01-12 11 views
8

Nuevamente me disculpo por una pregunta que podría ser simple para todos ustedes. Tengo una comprensión limitada de lo que ocurre entre bambalinas en Silverlight.DispatcherTimer y límites de actualización de IU en C# silverlight

Tengo una aplicación de gráficos (Visiblox) que utilizo como un ámbito móvil actualizado cada 20 ms, agregando y eliminando un punto. En pseudocódigo:

List<Point> datapoints= new List<Point>(); 
Series series = new Series(datapoints); 
void timer_tick(){ 
    datapoints.Add(new Point); 
    datapoints.RemoveAt(0); 
    // no need to refresh chart, it does refresh automatically 
} 

Cuando se ejecuta 6 series en esta herramienta de gráficos, comenzó a mostrar un poco lento. Cambiar la marca a 10ms no hizo diferencia alguna, la tabla se actualizó a la misma velocidad, por lo que parece que 20ms es el límite de velocidad (¿UI o gráfico?).

Probé con CompositionTarget.Rendering y obtuve los mismos resultados: por debajo de 20 ms, no hubo diferencia en la velocidad.

Luego habilité accidentalmente ambos y dupliqué la velocidad. Así que probé con múltiples hilos (2, 3, 4) y la velocidad se duplicó, triplicó y cuadruplicó. Esto todavía no tiene bloqueos, ya que ni siquiera sé qué proceso necesito para generar un bloqueo, pero no tengo corrupción de datos ni pérdidas de memoria.

La pregunta que tengo es por qué un gráfico lento en 20 ms no puede ejecutarse en 10 ms, pero es ridículamente rápido cuando multiproceso? ¿El proceso de actualización de la interfaz de usuario se está ejecutando más rápido? ¿El cómputo del gráfico se ha duplicado? ¿O hay un límite de qué tan rápido se puede ejecutar un solo DispatcherTimer?

Gracias!


Editar: Tengo un fondo de codificación incorporado, así que cuando pienso en las discusiones y los tiempos, inmediatamente pienso en alternar un alfiler en el hardware y conectar un alcance para medir longitudes de proceso. Soy nuevo en los hilos en C# y no hay pines para conectar los alcances. ¿Hay alguna forma de ver los tiempos de los hilos gráficamente?

Respuesta

4

La clave aquí es que me doy cuenta de que Silverlight renderiza a una velocidad máxima de 60 fps por defecto (personalizable a través de su propiedad MaxFrameRate). Eso significa que los tics DispatcherTimer dispararán como máximo 60 veces por segundo. Además, todo el trabajo de renderizado ocurre también en el hilo de la interfaz de usuario, por lo que DispatcherTimer se activa a la velocidad que el dibujo está sucediendo en el mejor de los casos, como se indicó en el póster anterior.

El resultado de lo que está haciendo agregando tres temporizadores es simplemente disparar el método "agregar datos" 3 veces por bucle de evento en lugar de una vez, por lo que parecerá que sus gráficos van mucho más rápido, pero de hecho el la velocidad de fotogramas es más o menos la misma. Puede obtener el mismo efecto con un solo DispatcherTimer y simplemente agregar 3 veces más datos en cada Tick. Puede verificar esto enganchando en el evento Contentingget.Rendering y contando la velocidad de cuadro allí en paralelo.

El punto ObservableCollection realizado anteriormente es bueno pero en Visiblox hay un poco de magia para tratar de mitigar los efectos de eso, así que si está agregando datos a un ritmo muy rápido, las actualizaciones del gráfico se agruparán en la velocidad del ciclo de renderizado y las repeticiones innecesarias se eliminarán.

También con respecto a su punto de estar relacionado con la implementación ObservableCollection de IDataSeries, usted es completamente libre de implementar la interfaz IDataSeries usted mismo, por ejemplo al respaldarlo con una simple lista. Solo tenga en cuenta que, obviamente, si lo hace, el cuadro ya no se actualizará automáticamente cuando cambien los datos. Puede forzar una actualización de gráfico llamando a Chart.Invalidate() o cambiando un rango de eje establecido manualmente.

+0

Tienes razón. Es una ilusión A velocidades lentas, es más fácil rastrear el gráfico con los ojos y la lentitud se detecta fácilmente, pero al actualizar más de un punto a la vez, la lentitud es difícil de ver. ¡Gracias! – PaulG

7

Un DispatcherTimer, que activa su evento Tick en el hilo de la interfaz de usuario, es lo que se considera un temporizador de baja resolución o baja precisión porque su intervalo efectivamente significa "marcar apenas x desde el último tic". Si el hilo de la interfaz de usuario está ocupado haciendo cualquier cosa (procesando entrada, refrescando el gráfico, etc.) entonces retrasará los eventos del temporizador. Además, tener un montón de DispatcherTimer en el hilo de la interfaz de usuario en intervalos muy bajos también ralentizará la capacidad de respuesta de su aplicación porque, mientras se eleva el evento Tick, la aplicación no puede responder a la entrada.

Por lo tanto, como ha indicado, para procesar datos con frecuencia, debe pasar a una cadena de fondo. Pero hay advertencias. El hecho de que no estés observando corrupción u otros errores podría ser pura coincidencia. Si la lista se está modificando en un hilo de fondo al mismo tiempo que el hilo de primer plano intenta leer de él, eventualmente se bloqueará (si tiene suerte) o verá datos corruptos.

En su ejemplo, tiene un comentario que dice "no es necesario actualizar el gráfico, se actualiza automáticamente". Esto me hace preguntarme cómo sabe la tabla que ha cambiado la colección datapoints? List<T> no genera eventos cuando se modifica. Si estuvieras usando un ObservableCollection<T>, señalaría que cada vez que eliminas/agregas un punto estás refrescando potencialmente el gráfico, lo que podría estar ralentizando las cosas.

Pero si de hecho está usando List<T>, entonces debe haber algo más (¿quizás otro temporizador?) Que esté refrescando el gráfico. ¿Tal vez el control de gráfico en sí tiene un mecanismo incorporado de actualización automática?

En cualquier caso, el problema es un poco complicado but not completely new. Hay formas en que puede mantener una colección en un hilo de fondo y enlazar desde el hilo de la interfaz de usuario. Pero cuanto más rápido se actualice su UI, más probable es que espere a que un hilo de fondo libere un bloqueo.

Una forma de minimizar esto sería usar un LinkedList<T> en lugar de List<T>. Agregar al final de una LinkedList es O (1), por lo que se elimina un elemento. Un List<T> necesita cambiar todo hacia abajo por uno cuando elimina un elemento desde el principio. Al utilizar LinkedList, puede bloquearlo en el (los) hilo (s) de fondo y minimizará la cantidad de tiempo que mantiene el bloqueo. En el hilo de la interfaz de usuario también necesitaría obtener el mismo bloqueo y copiar la lista a una matriz o actualizar la tabla mientras se mantiene el bloqueo.

Otra solución posible sería almacenar en el búfer "trozos" de puntos en el subproceso de fondo y publicar un lote de ellos en el subproceso de la interfaz de usuario con Dispatcher.BeginInvoke, donde podría actualizar de forma segura una colección.

+0

Gracias por la gran visión. Para responder algunas de sus inquietudes, no estoy usando exactamente una Lista <>, sino una implementación de una colección que tiene INotifyCollectionChanged con ella. Dado que esta es una aplicación de terceros, no hay mucho que pueda hacer al respecto. Mi primer acercamiento a esto fue exactamente como sugirió, estaba renderizando un mapa de bits en el fondo mientras estaba desplazando un mapa de bits en primer plano. Pero debido a las limitaciones de tiempo y la calidad de mi herramienta de creación de gráficos 'hecha en casa', opté por una de terceros. Así que LinkedList y otros están fuera, necesito usar su implementación específica ... – PaulG

+0

(continuación, se quedó sin caracteres) Otra cosa, me acabo de dar cuenta de que System.Windows.Threading crea hilos que se ejecutan en el mismo hilo que la interfaz de usuario, de lo contrario, no sería posible actualizar la interfaz de usuario. ¿Derecha? Entonces, el hecho de que estaba pensando que estoy multiproceso debería ser incorrecto. ¿Qué está pasando entonces? ¿Silverlight asigna segmentos de tiempo para compartir entre UI y DispatchTimer y el hecho de que haya creado varios de ellos aumenta la prioridad de las tareas del temporizador? – PaulG

+0

(continuación III) Esta es también la razón por la que creo que no estoy viendo fallos o errores, porque en realidad no estoy ejecutando varios hilos, sino solo uno tras otro.Mi aplicación es una herramienta de simulación que procesa miles de cálculos matriciales por período de temporizador, lo que resulta en millones de cálculos por segundo. Debería haber chocado por ahora. Por favor, corríjame si estoy equivocado. Estas son todas las suposiciones. – PaulG

Cuestiones relacionadas