2012-02-08 10 views
6

tengo este flujo de datos, más o menos:Java + swing: escribir código para unirse eventos de cambio

DataGenerator -> DataFormatter -> UI 

DataGenerator es algo que genera datos con rapidez; DataFormatter es algo que lo formatea con fines de visualización; y la interfaz de usuario es solo un grupo de elementos Swing.

me gustaría hacer mi DataGenerator algo como esto:

class DataGenerator 
{ 
    final private PropertyChangeSupport pcs; 
    ... 
    public void addPropertyChangeListener(PropertyChangeListener pcl) { 
    this.pcs.addPropertyChangeListener(pcl); 
    } 
    public void removePropertyChangeListener(PropertyChangeListener pcl) { 
    this.pcs.removePropertyChangeListener(pcl); 
    } 
} 

y simplemente llamar this.pcs.firePropertyChange(...) cada vez que mi generador de datos tiene nuevos datos; entonces puedo simplemente hacer dataGenerator.addPropertyListener(listener) donde listener es responsable de empujar el cambio hacia el DataFormatter y luego hacia la UI.

El problema con este enfoque es que hay miles de cambios en dataGenerator por segundo (entre 10,000 y 60,000 por segundo dependiendo de mi situación), y el costo computacional de formatearlo para la UI es lo suficientemente alto como para carga innecesaria en mi CPU; Realmente, todo lo que me importa visualmente es un máximo de 10-20 cambios por segundo.

¿Hay alguna manera de utilizar un enfoque similar, pero unir los eventos de cambio antes de que lleguen al DataFormatter? Si recibo múltiples eventos de actualización en un solo tema, me importa mostrar el último y puedo omitir todos los anteriores.

+0

¿Qué tal mantener un valor largo con el System.nanoTime de la última UI- actualizar, e ignorar los eventos de cambio de propiedad si ocurren N ns después de una actualización? – aioobe

+0

Pensé en eso (diablos, 'System.currentTimeMillis()' funcionaría, me importa si las cosas son rápidas en relación con la percepción humana), pero hay un problema. Supongamos que tiene un evento de cambio 100 microsegundos después de una actualización visual, por lo que DataFormatter/UI ignora el cambio. Ahora, por alguna razón, DataGenerator deja de producir actualizaciones (no es necesariamente 10-60K eventos por segundo continuamente). Vaya, te has perdido el cambio más reciente. Eso es malo. –

Respuesta

4

dos ideas:

  • agregados PropertyChangeEvent s. Extienda PropertyChangeSupport, sobrescriba public void firePropertyChange(PropertyChangeEvent evt), dispare solo si el último evento se disparó hace más de 50ms (o el tiempo que parezca apropiado). (De hecho, debe sobrescribir cada método fire* o al menos el que usa en su escenario para evitar la creación del PropertyChangeEvent.)
  • Coloque todo el evento basado en aproximación. 60,000 eventos por segundo es un número bastante alto. En esta situación yo encuestaría. Es un cambio conceptual en MVP donde el presentador sabe si está en estado activo y debe sondear. Con este enfoque, no se generan miles de eventos inútiles; incluso puede presentar los cuadros más altos posibles por segundo, sin importar la cantidad de datos que haya. O puede configurar el presentador a una velocidad fija, dejarlo dormir entre actualizaciones durante un tiempo determinado, o dejar que se adapte a otras circunstancias (como la carga de la CPU).

Tendería al segundo enfoque.

+0

OK. Para su segunda aproximación, ¿hay alguna manera de usar el sondeo (puedo mantener fácilmente un tipo de bandera "cambiada" usando AtomicBoolean) pero aun así mantener un desacoplamiento entre las dos partes? No quiero que mi DataGenerator tenga que saber sobre su cliente downstream; además, mi enunciado de problema fue un poco simplificado: en realidad hay 256 DataGenerators con un agregado de 10-60K cambios por segundo. (la mayoría de las veces solo unas pocas están activas, pero no puedo predecir cuáles son en un instante dado. Es una larga historia). El enfoque de cambio de propiedad es bueno, ya que solo me notifican cuando sea necesario. –

+0

Pero todavía tiene que registrarse en algún momento al principio. ¿Importa si está almacenando la referencia para sondeo o llamando a 'reference.addPropertyChangeListener (this)'? Estoy de acuerdo en que puede ser un concepto más limpio tener un modelo activo, desacoplado, pero no con esta cantidad de datos (y el patrón MVP cambia las responsabilidades por tales razones). Escribiría un 'Agregador' que conoce tus 'DataGenerators', recopila los datos y el presentador puede sondearlos. De hecho, este 'Agregador 'incluso podría enviar 20 eventos por segundo. Simplemente no veo 60,000 procesos de eventos como eficientes o útiles. –

+0

Ah - Me gusta la idea del agregador, ¡gracias! –

0

Si escribe su propio pequeño cambio oyente que tiene un indicador de bloqueo y un contador de tiempo, puede:

syncronized onChangeRequest() { 
    if (flag) { 
     flag = false; 
     startTimer(); 
    } 
} 

timerEvent() { 
    notify all your listeners; 
} 

creo que en realidad hay un excelente indicador de concurrencia de bloqueo que se podría utilizar pero no puedo por la mi vida recuerda lo que se llama!

1

Se puede usar un ArrayBlockingQueue de tamaño 1, en el que la empujaría sus datos con la función offer() (significa que si la cola está llena, no hace nada)

continuación, crear un ScheduledThreadPoolExecutor que sondea periódicamente la cola.

De esta manera se pierde el acoplamiento entre la generación y la pantalla.

Generador -> Poner en cola -> Formato/Pantalla

+0

Pero no quiero una cola; Quiero unir eventos.Si un DataGenerator dice "¡Mi valor es azul!" y luego "¡Mi valor es rojo!" y luego más tarde "¡Mi valor es púrpura!" No me importan las dos primeras, solo me importa la última. –

3

Parece que su DataGenerator haciendo un montón de trabajo no GUI en la EDT-hilo. Yo recomendaría que su DataGenerator extienda SwingWorker y haciendo el trabajo en un hilo de fondo, implementado en doInBackground. El SwingWorker podría obtener publish resultados intermedios en la GUI, mientras que usted tiene un método process que recibe los últimos fragmentos publicados en el EDT y actualiza su GUI.

El SwingWorkers process método hace coalesce los published trozos, por lo que no se ejecutará una vez por cada resultado intermedio publicada.

Si sólo la atención del último resultado en la EDT puede utilizar este código, que sólo se preocupa por el último trozo de la lista:

@Override 
protected void process(List<Integer> chunks) { 

    // get the *last* chunk, skip the others 
    doSomethingWith(chunks.get(chunks.size() - 1)); 
} 

Leer más en SwingWorker: Tasks that Have Interim Results.

+0

DataGenerator no está en el hilo EDT y por diversos motivos sucede en otro hilo gestionado por un ExecutorService. –

+0

@JasonS: Ya veo. Pero 'SwingWorker' también se puede ejecutar en un' ExecutorService', consulte la documentación de 'SwingWorker':' Debido a que SwingWorker implementa Runnable, un SwingWorker puede enviarse a un Executor para su ejecución. – Jonas

+0

@JasonS: Tengo un ejemplo completo en cómo utilizar el último resultado intermedio de un SwingWorker en mi respuesta a [Detener/cancelar el hilo de SwingWorker?] (http://stackoverflow.com/a/9182043/213269) – Jonas

1

Otra posibilidad es simplemente agregar el oyente a su generador, pero en lugar de reaccionar directamente al cambio, acaba de iniciar un temporizador. Así que su oyente se vería así (en una especie de pseudo-código ya que soy demasiado perezoso para el fuego de mi IDE o buscar las firmas de los métodos exactos)

Timer timer = new Timer(100, new ActionListener(){//update the UI in this listener}; 

public void propertyChange(PropertyChangeEvent event){ 
if (timer.isRunning()){ 
    timer.restart(); 
} else { 
    timer.start(); 
} 
} 

Esto funcionará a menos que su generador de datos mantiene en la generación de los datos de las todo el tiempo, o si quieres actualizaciones intermedias también. En ese caso, puede simplemente eliminar la llamada timer.restart(), u optar por cualquiera de las otras sugerencias en este hilo (el mecanismo de sondeo, SwingWorker)