2009-03-02 6 views
10

Actualmente estoy diseñando/reelaborando la parte de enlace de datos de una aplicación que hace un uso intensivo de enlaces de datos de winforms y actualizaciones provenientes de un hilo de fondo (una vez por segundo en> 100 registros).WinForms escenario de enlace de datos de múltiples hilos, mejor práctica?

Supongamos que la aplicación es una aplicación de comercio de acciones, donde un hilo de fondo supervisa los cambios de datos y los coloca en los objetos de datos. Estos objetos se almacenan en un BindingList<> e implementan INotifyPropertyChanged para propagar los cambios a través de enlace de datos a los controles de winforms. Además, los objetos de datos actualmente están coordinando los cambios a través del WinformsSynchronizationContext.Send con el subproceso de la interfaz de usuario. El usuario puede ingresar algunos de los valores en la interfaz de usuario, lo que significa que algunos valores se pueden cambiar desde ambos lados. Y los valores del usuario no deben sobrescribirse por las actualizaciones.

por lo que hay varias pregunta viene a la mente:

  • ¿Hay un diseño de guildline general de cómo hacer eso (actualizaciones en segundo plano en el enlace de datos)?
  • ¿Cuándo y cómo realizar una reunión en el hilo de la interfaz de usuario?
  • ¿Cuál es la mejor forma de que el hilo de fondo interactúe con binding/data objects?
  • ¿Qué clases/interfaces deben usarse? (BindingSource, ...)
  • ...

La interfaz de usuario en realidad no saben que hay un hilo de fondo, que actualiza el control, ya partir de mi entendimiento en los escenarios de enlace de datos de la interfaz de usuario no deben' sé de dónde provienen los datos ... Puedes pensar en el hilo de fondo como algo que empuja los datos a la interfaz de usuario, así que no estoy seguro si el backgroundworker es la opción que estoy buscando.

A veces desea obtener alguna respuesta de IU durante una operación en el objeto de datos/negocio (por ejemplo, establecer el fondo durante los nuevos cálculos). No basta con aumentar una propiedad modificada en una propiedad de estado que está vinculada al fondo, ya que el control se vuelve a pintar después de que el cálculo ha finalizado. Mi idea sería enlazar el evento propertychanged y llamar a .update() en el control ... ¿Alguna otra idea al respecto?

Respuesta

6

Este es un problema difícil ya que la mayoría de las “soluciones” conducen a una gran cantidad de código personalizado y un montón de llamadas a BeginInvoke() o System.ComponentModel.BackgroundWorker (que en sí es simplemente una envoltura fina sobre BeginInvoke).

En el pasado, también me he dado cuenta que pronto desea retrasar el envío de sus INotifyPropertyChanged eventos hasta que los datos es estable. El código que maneja un evento modificado correctamente a menudo necesita leer otras propiedades. También suele tener un control que necesita volver a dibujarse cada vez que cambia el estado de una de muchas propiedades, y no desea que el control se vuelva a dibujar con demasiada frecuencia.

En primer lugar, cada control WinForms personalizados debe leer todos los datos que necesita para pintarse en el controlador PropertyChanged evento, por lo que no necesita para bloquear los objetos de datos cuando se trataba de un (OnPaint) WM_PAINT mensaje. El control no debe repintarse inmediatamente cuando obtiene nuevos datos; en su lugar, debe llamar al Control.Invalidate(). Windows combinará los mensajes WM_PAINT en la menor cantidad posible de solicitudes y solo los enviará cuando el hilo de la interfaz de usuario no tenga nada más que hacer. Esto minimiza la cantidad de redibujados y el tiempo de bloqueo de los objetos de datos. (Los controles estándar principalmente hacen esto con el enlace de datos de todos modos)

Los objetos de datos necesitan registrar qué ha cambiado a medida que se realizan los cambios, una vez que se ha completado un conjunto de cambios, "patear" el hilo de UI para llamar al SendChangeEvents método que luego llama al controlador de eventos PropertyChanged (en el hilo de UI) para todas las propiedades que han cambiado. Mientras se ejecuta el método SendChangeEvents(), los objetos de datos deben estar bloqueados para evitar que los hilos de fondo los actualicen.

El hilo de la interfaz de usuario se puede "patear" con una llamada al BeginInvoke cada vez que se haya leído un conjunto de actualizaciones de la base de datos. A menudo es mejor tener la encuesta de hilos de la interfaz de usuario usando un temporizador, ya que Windows solo envía el mensaje WM_TIMER cuando la cola de mensajes de la interfaz de usuario está vacía, lo que hace que la interfaz de usuario se sienta más receptiva.

Considere también no utilizar ningún enlace de datos, y hacer que la IU pregunte a cada objeto de datos "qué ha cambiado" cada vez que se dispara el temporizador. La vinculación de datos siempre se ve bien, pero puede convertirse rápidamente en parte del problema, en lugar de ser parte de la solución.

Como el bloqueo/desbloqueo de los objetos de datos es molesto y puede no permitir que las actualizaciones se lean desde la base de datos lo suficientemente rápido, es posible que desee pasar el subproceso de UI una copia (virtual) de los objetos de datos. Tener el objeto de datos sea persistente/inmutable para que cualquier cambio en el objeto de datos devuelva un nuevo objeto de datos en lugar de cambiar el objeto de datos actual puede habilitar esto.

Los objetos persistentes suenan muy lento, pero no es necesario, consulte this y that para algunos punteros. También mire this y that en Stack Overflow.

También eche un vistazo a retlang - Message-based concurrency in .NET. Su lote de mensajes puede ser útil.

(Para WPF, tendría un View-Model que establece en el subproceso de interfaz de usuario que luego se actualizó en 'lotes' del modelo de subprocesos múltiples por el hilo de fondo. Sin embargo, WPF es mucho mejor al combinar datos eventos vinculantes luego WinForms.)

2

Hay un MSDN article específico sobre ese tema. Pero prepárate para mirar VB.NET. ;)

Además, tal vez podría usar System.ComponentModel.BackgroundWorker, en lugar de un segundo hilo genérico, ya que formaliza muy bien el tipo de interacción con el hilo de fondo generado que está describiendo. El ejemplo dado en la biblioteca de MSDN es bastante decente, así que ve a buscar una pista sobre cómo usarlo.

Editar: Tenga en cuenta: No se requiere coordinación si utiliza el evento ProgressChanged para comunicarse de nuevo con el subproceso de interfaz de usuario. El hilo de fondo llama a ReportProgress cada vez que tiene la necesidad de comunicarse con la IU. Como es posible adjuntar cualquier objeto a ese evento, no hay ninguna razón para realizar una clasificación manual. El progreso se comunica a través de otra operación asíncrona, por lo que no hay necesidad de preocuparse por cuán rápido la UI puede manejar los eventos de progreso ni si el hilo de fondo se interrumpe esperando a que el evento finalice.

Si prueba que el hilo de fondo está elevando el progreso del evento cambiado demasiado rápido, entonces puede consultar Pull vs. Push models for UI updates un excelente artículo de Ayende.

+1

System.ComponentModel.BackgroundWorker, puede ser parte de la solución, sin embargo, el problema difícil es cómo mantener los datos de actualizaciones rápidas sin mirar el hilo de interfaz de usuario. Hacer lo anterior sin tener que tener ninguna otra línea de código que tenga que pensar en bloquear o cruzar llamadas de hilo. –

+0

Si nos fijamos en el Backgroundworker, no hay mucha diferencia en generar un thread y Marshal con WindowsFormsSynchronizationContext, el BW hace lo mismo. –

+0

Consulte la edición de la respuesta. – Dun3

0

Este es un problema que he resuelto en Update Controls. Traigo esto para no sugerirte que reescribas tu código, sino para darte alguna fuente para buscar ideas.

La técnica que utilicé en WPF era utilizar Dispatcher.BeginInvoke para notificar al hilo de primer plano de un cambio. Puede hacer lo mismo en Winforms con Control.BeginInvoke. Lamentablemente, debe pasar una referencia a un objeto Form en su objeto de datos.

Una vez hecho, se puede pasar de una acción en BeginInvoke que dispara PropertyChanged. Por ejemplo:

_form.BeginInvoke(new Action(() => NotifyPropertyChanged(propertyName)))); 

Deberá bloquear las propiedades de su objeto de datos para que sean seguras para la ejecución de subprocesos.

2

estructuras Sí todos los libros muestran roscados e invoca etc. que es perfectamente correcto, etc, pero puede ser un dolor de código, y con frecuencia difícil de organizar para que pueda hacer las pruebas dignas para que

una interfaz de usuario única necesita actualizarse tantas veces por segundo, por lo que el rendimiento nunca es un problema, y ​​el sondeo funcionará bien

Me gusta usar un gráfico de objetos que se actualiza continuamente por un grupo de hilos de fondo. Comprueban los cambios reales en los valores de los datos y cuando notan un cambio real actualizan un contador de versiones en la raíz del gráfico de objetos (o en cada elemento principal lo que tenga más sentido) y actualiza los valores

Luego su proceso en primer plano puede tener un temporizador (igual que el hilo UI por defecto) para disparar una vez por segundo y verificar el contador de versiones, y si cambia, lo bloquea (para detener actualizaciones parciales) y luego actualiza la pantalla

Esta técnica simple aísla totalmente el hilo de interfaz de usuario desde el fondo enhebra

+0

He encontrado que los temporizadores funcionan bien en el pasado. –

1

Acabo de luchar en una situación similar - subproceso badkground actualizando la interfaz de usuario a través de BeginInvokes. El fondo tiene una demora de 10ms en cada ciclo, pero en el camino me encontré con problemas donde las actualizaciones de UI que a veces se disparan cada vez en ese ciclo, no pueden mantenerse al día con la frecuencia de actualizaciones, y la aplicación deja de funcionar de manera efectiva (no estoy seguro de lo que sucede, ¿explotó una pila?).

Terminé agregando una bandera en el objeto pasado sobre la invocación, que era solo una bandera lista. Establecí esto en falso antes de llamar al invoke, y luego el subproceso bg no volvería a hacer más actualizaciones de UI hasta que este indicador vuelva a ser verdadero. El subproceso de interfaz de usuario haría sus actualizaciones de pantalla, etc., y luego establecería esta var en verdadero.

Esto permitió que el subproceso bg siguiera crujiendo, pero permitió que la interfaz de usuario cortara el flujo hasta que estuviera listo para más.

+0

la única parte que no estoy seguro con esto, es por qué se permite que la bandera sea accesible desde ambos hilos. pero parece funcionar. –

0

Cree un nuevo UserControl, agregue su control y formatéelo (quizás dock = fill) y agregue una propiedad. ahora configure la propiedad para invocar el control de usuario y actualice su elemento, ¡cada vez que cambie la propiedad de cualquier hilo que desee!

eso es mi solución:

private long value; 
    public long Value 
    { 
     get { return this.value; } 
     set 
     { 
      this.value = value; 

      UpdateTextBox(); 
     } 
    } 

    private delegate void Delegate(); 
    private void UpdateTextBox() 
    { 
     if (this.InvokeRequired) 
     { 
      this.Invoke(new Delegate(UpdateTextBox), new object[] {}); 
     } 
     else 
     { 
      textBox1.Text = this.value.ToString(); 
     } 
    } 

en mi forma Ato mi punto de vista

viewTx.DataBindings.Add(new Binding("Value", ptx.CounterTX, "ReturnValue")); 
0

Este post es viejo pero pensé que le daría opciones a los demás. Parece que una vez que comienzas a hacer la programación asíncrona y la unión de datos de Windows Forms terminas con problemas al actualizar el origen de datos de Bindingsource o al actualizar las listas vinculadas al control de formularios de Windows. Voy a intentar usar la clase Jeffrey Richters AsyncEnumerator de sus herramientas powerthreading en wintellect.

Motivo: 1.Su clase AsyncEnumerator dirige automáticamente los hilos de fondo a los subprocesos de la interfaz de usuario para que pueda actualizar los controles como lo haría con el código sincrónico. 2. AsyncEnumerator simplifica la programación Async. Lo hace de forma automática, por lo que escribe su código de forma sincrónica, pero el código todavía se está ejecutando de manera asincrónica.

Jeffrey Richter tiene un video en Channel 9 MSDN, que explica AsyncEnumerator.

Deséenme suerte.

-R

Cuestiones relacionadas