Многопоточность индикатора выполнения MVC

Я использую шаблон MVC для своего дизайна, когда пользователь нажимает кнопку поиска, я вызываю поиск в модели, но я также хочу обновить индикатор выполнения с информацией, возвращенной из этой модели.

Я пытался использовать свингворкер, но индикатор выполнения не обновляется. Я подозреваю, что делаю что-то не так с потоками.

Моя кнопка, определенная в контроллере:

 class SearchBtnListener implements ActionListener {
        public void actionPerformed(ActionEvent e) {
            _view.displayProgress();  
        }    
}

Это вызывает поиск в модели и имеет следующий вызов в представлении:

public void displayProgress() {

    TwoWorker task = new TwoWorker();
    task.addPropertyChangeListener(new PropertyChangeListener() {

         @Override
         public void propertyChange(PropertyChangeEvent e) {
             if ("progress".equals(e.getPropertyName())) {
                _progressBar.setValue((Integer) e.getNewValue());
             }
         }

     });
     task.execute();             
}      


private class TwoWorker extends SwingWorker<Void, Void> {        
    @Override
    protected Void doInBackground() throws Exception {
        _model.startSearch(getTerm());                  // time intensive code
        File file = new File("lock");           
        while (file.exists()){
            setProgress(_model.getStatus());
            System.out.println(_model.getStatus()); // never called
        }           
        return null;
    }  

    protected void done(){
        updateMain();
    }
}

Фиктивная функция, определенная в модели для тестирования:

public int getStatus(){
    Random r = new Random();
    return r.nextInt();
}

person Neutralise    schedule 04.04.2011    source источник


Ответы (1)


Не звони

_progressBar.setValue(_model.getStatus());

изнутри вашего SwingWorker, поскольку это вызывает код Swing из фонового потока, и в любом случае это то, для чего предназначен PropertyChangeListener. Вместо этого просто установите свойство прогресса, вот и все.

Кроме того, не вызывайте done() из метода doInBackground, так как SwingWorker должен вызывать его из EDT. Так что пусть SwingWorker сам вызовет этот метод, когда это действительно будет сделано.

Кроме того, Done() следует делать() - первая буква не должна быть заглавной, и вы должны использовать аннотации @Override в этом коде, чтобы быть уверенным, что вы правильно переопределяете методы.

Кроме того, что это делает?

 _model.startSearch(_view.getTerm());

Вызывает ли он код, выполнение которого занимает некоторое время? Следует ли это инициализировать из самого SwingWorker doInBackground?

Редактировать: Другой вариант - дать модели связанное свойство int, скажем, называемое прогрессом, а затем добавить к нему PropertyChangeListener напрямую, позволяя ему обновлять JProgressBar. Например,

import java.awt.BorderLayout;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

import javax.swing.*;

public class MVC_ProgressBarThread {
   private static void createAndShowUI() {
      MVC_View view = new MVC_View();
      MVC_Model model = new MVC_Model();
      MVC_Control control = new MVC_Control(view, model);
      view.setControl(control);

      JFrame frame = new JFrame("MVC_ProgressBarThread");
      frame.getContentPane().add(view);
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.pack();
      frame.setLocationRelativeTo(null);
      frame.setVisible(true);
   }

   public static void main(String[] args) {
      java.awt.EventQueue.invokeLater(new Runnable() {
         public void run() {
            createAndShowUI();
         }
      });
   }
}

@SuppressWarnings("serial")
class MVC_View extends JPanel {
   private MVC_Control control;
   private JProgressBar progressBar = new JProgressBar();
   private JButton startActionButton = new JButton("Start Action");

   public MVC_View() {
      startActionButton.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            buttonActionPerformed();
         }
      });

      JPanel buttonPanel = new JPanel();
      buttonPanel.add(startActionButton);
      setLayout(new BorderLayout());
      add(buttonPanel, BorderLayout.NORTH);
      add(progressBar, BorderLayout.CENTER);
   }

   public void setControl(MVC_Control control) {
      this.control = control;
   }

   private void buttonActionPerformed() {
      if (control != null) {
         control.doButtonAction();
      }
   }

   public void setProgress(int progress) {
      progressBar.setValue(progress);
   }

   public void start() {
      startActionButton.setEnabled(false);
   }

   public void done() {
      startActionButton.setEnabled(true);
      setProgress(100);
   }
}

class MVC_Control {
   private MVC_View view;
   private MVC_Model model;

   public MVC_Control(final MVC_View view, final MVC_Model model) {
      this.view = view;
      this.model = model;
      model.addPropertyChangeListener(new PropertyChangeListener() {
         public void propertyChange(PropertyChangeEvent pce) {
            if (MVC_Model.PROGRESS.equals(pce.getPropertyName())) {
               view.setProgress((Integer)pce.getNewValue());
            }
         }
      });
   }

   public void doButtonAction() {
      view.start();
      SwingWorker<Void, Void> swingworker = new SwingWorker<Void, Void>() {
         @Override
         protected Void doInBackground() throws Exception {
            model.reset();
            model.startSearch();
            return null;
         }

         @Override
         protected void done() {
            view.done();
         }
      };
      swingworker.execute();
   }

}

class MVC_Model {
   public static final String PROGRESS = "progress";
   private static final int MAX = 100;
   private static final long SLEEP_DELAY = 100;
   private int progress = 0;
   private PropertyChangeSupport pcs = new PropertyChangeSupport(this);

   public void setProgress(int progress) {
      int oldProgress = this.progress;
      this.progress = progress;

      PropertyChangeEvent evt = new PropertyChangeEvent(this, PROGRESS, oldProgress, progress);
      pcs.firePropertyChange(evt);
   }

   public void reset() {
      setProgress(0);
   }

   public void addPropertyChangeListener(PropertyChangeListener listener) {
      pcs.addPropertyChangeListener(listener);
   }

   public void startSearch() {
      for (int i = 0; i < MAX; i++) {
         int newValue = (100 * i) / MAX;
         setProgress(newValue);
         try {
            Thread.sleep(SLEEP_DELAY);
         } catch (InterruptedException e) {}
      }
   }
}
person Hovercraft Full Of Eels    schedule 04.04.2011
comment
Я изменил свой код в соответствии с вашими предложениями, да, тяжелый код — это startSearch() в модели, view.getTerm() возвращает текст, который пользователь ввел в текстовое поле. Если я включу startsearch в процедуру doinbackground, нужно ли будет убрать отрисовку индикатора выполнения? - person Neutralise; 04.04.2011
comment
Опять же, в doInBackground не должно быть кода, относящегося к индикатору выполнения или любому компоненту Swing. Несмотря ни на что, уберите это из doInBackground. И да, если код поиска модели занимает много времени, не должен ли он быть тем, что принадлежит фоновому потоку? Вот для чего вы используете SwingWorker. - person Hovercraft Full Of Eels; 04.04.2011
comment
Я думал, что если я вызову startSearch в основном потоке, индикатор выполнения обновится в новом потоке. Это были мои мысли, но, похоже, я ошибаюсь. - person Neutralise; 04.04.2011
comment
Вы блокируете EDT, и опять же, это цель использования SwingWorker, чтобы предотвратить это. Прочтите руководство Параллелизм в Swing, чтобы понять, что происходит. - person Hovercraft Full Of Eels; 04.04.2011
comment
Кажется, я был преждевременным! Теперь он правильно рисует строку состояния, но фактический статус не обновляется. Я поместил фиктивную функцию в свой модельный класс: public int getStatus(){ Random r = new Random(); вернуть r.nextInt();}. Но строка состояния не обновляется. Было бы проще, если бы я загрузил свою новую кодовую базу, чтобы вы могли видеть, что происходит? - person Neutralise; 04.04.2011
comment
Кажется, потребуется больше усилий, чтобы решить проблему. Я предлагаю вам создать и опубликовать SSCCE в исходном сообщении, а затем опубликовать комментарий. - person Hovercraft Full Of Eels; 04.04.2011
comment
Обновлено. Я не могу загрузить что-то компилируемое, так как это задание, и я не могу опубликовать его. Прости. - person Neutralise; 04.04.2011
comment
ваш _model.startSearch(getTerm()); скорее всего блокирует. Может ли сама модель возвращать промежуточные результаты? - person Hovercraft Full Of Eels; 04.04.2011
comment
Ты прав. Я ставлю println до и после вызова моделей, чтобы начать поиск, и он блокируется здесь, пока поиск не будет завершен. Файл блокировки существует только во время поиска модели, что означает, что сам цикл для установки индикатора выполнения никогда не будет истинным. Нужно ли вызывать startsearch в другом вложенном потоке? Или вызвать его из основного потока? - person Neutralise; 04.04.2011
comment
Если у модели есть промежуточные результаты, вы можете сделать так, чтобы модель вызывала общедоступный метод SwingWorker, который вызывает публикацию, а затем устанавливает свойство в методе процесса. Или модель может быть SwingWorker. - person Hovercraft Full Of Eels; 04.04.2011
comment
Модель ищет файлы из папки с файлами X. Функция поиска на этот раз выполняет интенсивный поиск, вы говорите, что я должен попытаться обновить модель рабочего процесса в представлении через n интервалов? - person Neutralise; 04.04.2011
comment
@Neutralise: @Hovercraft, полный угрей, верен. здесь приведен короткий пример, который может быть полезен. - person trashgod; 04.04.2011
comment
@trashgod, да, я согласен, что это правильный метод. У меня просто проблемы с реализацией. - person Neutralise; 04.04.2011
comment
Установив индикатор выполнения в промежуточный режим, я вижу, что он рисуется правильно. Итак, теперь проблема заключается в том, как я получаю статус от моей модели. Может быть, нужна еще одна нить там, кажется. - person Neutralise; 04.04.2011
comment
см. новое предложение относительно придания модели связанного свойства и его прослушивания. - person Hovercraft Full Of Eels; 04.04.2011