Java + Swing: написание кода для объединения событий изменений

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

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 ответов

Решение

Две идеи:

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

Я бы склонялся ко второму подходу.

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

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: задачи, которые дают промежуточные результаты.

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

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

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

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

Другая возможность - просто добавить слушателя к вашему генератору, но вместо того, чтобы напрямую реагировать на изменения, вы просто запускаете таймер. Таким образом, ваш слушатель будет выглядеть (в виде псевдокода, так как мне лень запускать мою 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)

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

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

timerEvent() {
    notify all your listeners;
}

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

Другие вопросы по тегам