Java + Swing: написание кода для объединения событий изменений - PullRequest
6 голосов
/ 09 февраля 2012

У меня есть этот поток данных, примерно:

DataGenerator -> DataFormatter -> UI

DataGenerator - это то, что генерирует данные быстро;DataFormatter - это то, что форматирует его для целей отображения;а пользовательский интерфейс - это просто набор элементов Swing.

Я бы хотел сделать мой DataGenerator примерно таким:

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

и просто вызывать this.pcs.firePropertyChange(...) всякий раз, когда в моем генераторе данных появляются новыеданные;тогда я могу просто сделать dataGenerator.addPropertyListener(listener), где listener отвечает за передачу изменений в DataFormatter, а затем в пользовательский интерфейс.

Проблема этого подхода заключается в том, что в секунду происходят тысячи изменений DataGenerator.(от 10000 до 60000 в секунду в зависимости от моей ситуации), и вычислительные затраты на его форматирование для пользовательского интерфейса достаточно высоки, что создает ненужную нагрузку на мой процессор;на самом деле все, что меня беспокоит визуально, - это максимум 10-20 изменений в секунду.

Можно ли использовать подобный подход, но объединить события изменений, прежде чем они попадут в DataFormatter?Если я получаю несколько событий обновления по одной теме, я просто хочу отобразить самую последнюю и могу пропустить все предыдущие.

Ответы [ 5 ]

4 голосов
/ 09 февраля 2012

Две идеи:

  • Совокупный PropertyChangeEvent с. Расширение PropertyChangeSupport, перезапись public void firePropertyChange(PropertyChangeEvent evt), запуск только в том случае, если последнее событие было инициировано более 50 мс (или в любое другое время). (На самом деле вы должны перезаписать каждый fire* метод или хотя бы тот, который вы используете в своем сценарии, чтобы предотвратить создание PropertyChangeEvent.)
  • Отбросить все события на основе подхода. 60 000 событий в секунду - это довольно большое число. В этой ситуации я бы опросил. Это концептуальное изменение в MVP, когда докладчик знает, находится ли он в активном состоянии и должен ли проводить опрос. При таком подходе вы не генерируете тысячи бесполезных событий; Вы можете даже представить максимально возможное количество кадров в секунду, независимо от количества данных. Или вы можете установить для докладчика фиксированную частоту, оставить его в режиме ожидания между обновлениями на определенное время или адаптировать его к другим обстоятельствам (например, нагрузке на процессор).

Я бы предпочел второй подход.

3 голосов
/ 09 февраля 2012

Похоже, вы DataGenerator выполняете большую часть работы без GUI в потоке EDT. Я бы порекомендовал вам DataGenerator продлить SwingWorker и тем самым выполнить работу в фоновом потоке, реализованном в doInBackground. Затем SwingWorker может publish передавать результаты в GUI, в то время как у вас есть метод process, который получает последние несколько опубликованных блоков в EDT и обновляет ваш GUI.

Метод SwingWorkers process делает объединение блоков published, поэтому он не будет запускаться один раз для каждого опубликованного промежуточного результата.

Если вы заботитесь только о последнем результате в EDT, вы можете использовать этот код, который заботится только о последнем фрагменте в списке:

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

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

Подробнее о SwingWorker: задачи с промежуточными результатами .

1 голос
/ 09 февраля 2012

Другая возможность состоит в том, чтобы просто добавить слушателя к вашему генератору, но вместо прямой реакции на изменение вы просто запускаете таймер. Таким образом, ваш слушатель будет выглядеть (в виде псевдокода, так как мне лень запускать мою IDE или искать точные сигнатуры методов)

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();
 }
}

Это будет работать, если ваш генератор данных не продолжает генерировать данные все время, или если вы также хотите промежуточные обновления. В этом случае вы можете просто удалить вызов timer.restart() или выбрать другие предложения в этой теме (механизм опроса или SwingWorker)

1 голос
/ 09 февраля 2012

Вы можете использовать ArrayBlockingQueue размера 1, в котором вы будете отправлять данные с помощью функции offer() (значит, если очередь заполнена, она ничего не делает)

Затем создайте ScheduledThreadPoolExecutorкоторый периодически опрашивает очередь.

Таким образом вы теряете связь между генерацией и отображением.

Генератор -> Очередь -> Форматирование / Отображение

0 голосов
/ 09 февраля 2012

Если вы напишите свой собственный слушатель небольших изменений, который имеет флаг блокировки и таймер, вы можете:

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

timerEvent() {
    notify all your listeners;
}

Я полагаю, что на самом деле есть отличный флаг блокировки параллелизма, который можно использовать, но я не могу вспомнить, как он называется!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...