thread - Escenario de enlace de datos con múltiples subprocesos WinForms, ¿práctica recomendada?
ui thread c# (6)
Este es un problema que he resuelto en Controles de actualización . 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 fue usar Dispatcher.BeginInvoke para notificar el 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 que lo haga, puede pasar una Acción en BeginInvoke que activa PropertyChanged. Por ejemplo:
_form.BeginInvoke(new Action(() => NotifyPropertyChanged(propertyName))) );
Deberá bloquear las propiedades en su objeto de datos para que sean seguras para la ejecución de subprocesos.
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).
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 una 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 están coordinando los cambios a través de WinformsSynchronizationContext.Send
al hilo de UI. El usuario puede ingresar algunos de los valores en la IU, lo que significa que algunos valores pueden cambiarse desde ambos lados. Y los valores del usuario no deben sobrescribirse por las actualizaciones.
Entonces me vienen a la mente varias preguntas:
- ¿Hay una línea de diseño general que haga eso (actualizaciones de fondo en enlace de datos)?
- ¿Cuándo y cómo organizar el hilo de la interfaz de usuario?
- ¿Cuál es la mejor manera de que el hilo de fondo interactúe con los objetos de enlace / datos?
- ¿Qué clases / interfaces deberían usarse? (BindingSource, ...)
- ...
La IU realmente no sabe que hay un hilo de fondo, que actualiza el control y, a mi entender, en escenarios de enlace de datos, la IU no debería saber de dónde provienen los datos ... Puedes pensar en el hilo de fondo como algo que impulsa los datos a la interfaz de usuario, por lo que no estoy seguro si el backgroundworker es la opción que estoy buscando.
Algunas veces desea obtener alguna respuesta de IU durante una operación en el objeto de datos / negocio (por ejemplo, configurar el fondo durante los 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 conectar el evento propertychanged y llamar a .update () en el control ... ¿Alguna otra idea al respecto?
Sí, todos los libros muestran estructuras enhebradas e invocaciones, etc. Lo cual es perfectamente correcto, etc., pero puede ser difícil codificar y, a menudo, difícil de organizar para que puedas hacer pruebas decentes para ello.
Una IU solo necesita actualizarse tantas veces por segundo, por lo que el rendimiento nunca es un problema, y las encuestas funcionarán bien
Me gusta usar un gráfico de objetos que se actualiza continuamente mediante un conjunto 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 del objeto (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 subproceso 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 UI de los hilos de fondo
Acabo de pelear en una situación similar: thread de badkground actualizando la UI 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.
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!
esa 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 formulario ato mi vista
viewTx.DataBindings.Add(new Binding("Value", ptx.CounterTX, "ReturnValue"));
Este es un problema difícil ya que la mayoría de las "soluciones" conducen a un montón de código personalizado y muchas llamadas a BeginInvoke()
o System.ComponentModel.BackgroundWorker
(que en sí mismo es solo una capa delgada sobre BeginInvoke
).
En el pasado, también descubrí que pronto desea retrasar el envío de sus eventos INotifyPropertyChanged
hasta que los datos INotifyPropertyChanged
estables. 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 personalizado debe leer todos los datos que necesita para pintarse en el controlador de eventos PropertyChanged
, por lo que no necesita bloquear ningún objeto de datos cuando se trata de un mensaje WM_PAINT
( OnPaint
). El control no debe repintarse inmediatamente cuando obtiene nuevos datos; en su lugar, debe llamar a Control.Invalidate()
. Windows combinará los mensajes WM_PAINT
en la menor cantidad posible de solicitudes y solo los enviará cuando el subproceso UI no tenga nada más que hacer. Esto minimiza la cantidad de redibujados y el tiempo de bloqueo de los objetos de datos. (De todos modos, los controles estándar hacen esto con el enlace de datos)
Los objetos de datos necesitan registrar qué ha cambiado a medida que se realizan los cambios, luego de que se haya completado un conjunto de cambios, "patear" el hilo de la interfaz de usuario para llamar al método SendChangeEvents
que 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 UI se puede "patear" con una llamada a 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.
También considere no usar ningún enlace de datos, y haga 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 un problema 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 la interfaz de usuario 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, vea esto y lo otro para algunos indicadores. También vea esto y lo otro en .
También eche un vistazo a retlang: concurrencia basada en mensajes en .NET . Su lote de mensajes puede ser útil.
(Para WPF, tendría un View-Model que establezca el subproceso de UI que luego se actualizó en ''lotes'' del modelo de subprocesos múltiples por el subproceso de fondo. Sin embargo, WPF es mucho mejor al combinar eventos de enlace de datos que WinForms .)
Hay un artículo de MSDN 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 clasificación si utiliza el evento ProgressChanged para comunicarse de nuevo con el subproceso de la 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 compruebas que el hilo de fondo está elevando el evento de cambio de modo demasiado rápido, entonces tal vez quieras ver los modelos de Pull vs. Push para actualizaciones de UI, un excelente artículo de Ayende.