java - thread - ¿Cómo sincronizar el modelo Swing con un modelo "real" que cambia rápidamente?
java swing ui (3)
Como es sabido, todo lo relacionado con los componentes Swing debe hacerse en el hilo de despacho de eventos . Esto también se aplica a los models detrás de los componentes, como TableModel . Es bastante fácil en casos elementales, pero las cosas se complican bastante si el modelo es una "vista en vivo" de algo que debe ejecutarse en un hilo separado porque está cambiando rápidamente. Por ejemplo, una vista en vivo de un mercado de valores en un JTable. Los mercados de valores no suelen ocurrir en la EDT.
Entonces, ¿cuál es el patrón preferible para (des) acoplar el modelo Swing que debe estar en el EDT, y un modelo "real", seguro para subprocesos que debe ser actualizable desde cualquier lugar, en cualquier momento? Una posible solución sería dividir realmente el modelo en dos copias separadas: el modelo "real" más su homólogo de Swing, que es una instantánea del modelo "real". Entonces se sincronizan (bidireccionalmente) en la EDT de vez en cuando. Pero esto se siente como una hinchazón. ¿Es este realmente el único enfoque viable, o existen otras formas, o más estándar? Bibliotecas útiles? ¿Cualquier cosa?
El enfoque habitual es enviar "señales" de algún tipo a las que la UI escucha. En mi código, a menudo uso un despachador central que envía señales que contienen el objeto que se modificó, el nombre del campo / propiedad más el valor antiguo y el nuevo. No se envía ninguna señal para el caso oldValue.equals(newValue)
o oldValue.compareTo(newValue) == 0
(este último para fechas y BigDecimal
).
El hilo de la interfaz de usuario luego registra un oyente para todas las señales. Luego examina el objeto y el nombre y luego lo traduce a un cambio en la interfaz de usuario que se ejecuta a través de asyncExec()
.
Podría convertir eso en un oyente por objeto y hacer que cada elemento de la interfaz de usuario se registre en el modelo. Pero he encontrado que esto simplemente extiende el código por todo el lugar. Cuando tengo un gran conjunto de objetos en ambos lados, a veces solo uso varias señales (o eventos) para hacer las cosas más manejables.
Puedo recomendar el siguiente enfoque:
- Coloque los eventos que deberían modificar la tabla en una cola de "eventos pendientes", y cuando un evento se coloque en la cola y la cola esté vacía , invoque la secuencia de envío de eventos para vaciar la cola de todos los eventos y actualizar el modelo de tabla. Esta optimización significa que ya no está invocando el subproceso de distribución de eventos para cada evento recibido , lo que resuelve el problema de que el subproceso de distribución de eventos no se mantiene al día con la secuencia de eventos subyacente.
- Evite la creación de un nuevo Runnable al invocar el hilo de distribución de eventos utilizando una clase interna sin estado para vaciar la cola de eventos pendientes dentro de la implementación de su panel de tabla.
- Optimización adicional opcional: al vaciar la cola de eventos pendientes, minimice el número de eventos de actualización de la tabla activados recordando qué filas de la tabla deben ser repintadas y luego activando un solo evento (o un evento por fila) después de procesar todos los eventos.
Código de ejemplo
public class MyStockPanel extends JPanel {
private final BlockingQueue<StockEvent> stockEvents;
// Runnable invoked on event dispatch thread and responsible for applying any
// pending events to the table model.
private final Runnable processEventsRunnable = new Runnable() {
public void run() {
StockEvent evt;
while ((evt = stockEvents.poll() != null) {
// Update table model and fire table event.
// Could optimise here by firing a single table changed event
// when the queue is empty if processing a large #events.
}
}
}
// Called by thread other than event dispatch thread. Adds event to
// "pending" queue ready to be processed.
public void addStockEvent(StockEvent evt) {
stockEvents.add(evt);
// Optimisation 1: Only invoke EDT if the queue was previously empty before
// adding this event. If the size is 0 at this point then the EDT must have
// already been active and removed the event from the queue, and if the size
// is > 0 we know that the EDT must have already been invoked in a previous
// method call but not yet drained the queue (i.e. so no need to invoke it
// again).
if (stockEvents.size() == 1) {
// Optimisation 2: Do not create a new Runnable each time but use a stateless
// inner class to drain the queue and update the table model.
SwingUtilities.invokeLater(processEventsRunnable);
}
}
}
Según tengo entendido, no desea implementar interfaces de modelo Swing en su modelo real, ¿verdad? ¿Puede implementar un modelo Swing como una "vista" sobre una parte de un modelo real? Se traducirá su acceso de lectura getValueAt () a las llamadas del modelo real, y el modelo real notificará a Swing model sobre los cambios, ya sea proporcionando una lista de cambios o asumiendo que Swing se encargará de evaluar los nuevos valores de Todo lo que está mostrando actualmente.