Изящная обработка исключений в Swing Worker - PullRequest
23 голосов
/ 29 июня 2011

Я использую многопоточность в приложении через класс Swing Worker.Это работает нормально, но у меня плохое предчувствие, когда я показываю сообщение об ошибке в блоке try-catch.Может ли это потенциально заблокировать приложение?Вот как это выглядит прямо сейчас:

SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {

    // Executed in background thread
    public Void doInBackground() {
        try {
            DoFancyStuff();
        } catch (Exception e) {

            e.printStackTrace();

            String msg = String.format("Unexpected problem: %s", e
                    .toString());

            //TODO: executed in background thread and should be executed in EDT?
            JOptionPane.showMessageDialog(Utils.getActiveFrame(),
                    msg, "Error", JOptionPane.ERROR_MESSAGE,
                    errorIcon);

        }//END: try-catch

        return null;
    }

    // Executed in event dispatch thread
    public void done() {
        System.out.println("Done");
    }
};

Можно ли сделать это безопасным способом, используя среду Swing Worker?Является ли переопределение метода publish () хорошим лидером?приведет к созданию двух блоков try-catch, так как вычислительная часть генерирует исключения, так что я думаю, что в конце это будет чище.

Ответы [ 4 ]

58 голосов
/ 29 июня 2011

Правильный способ сделать это следующим образом:

SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {
    // Executed in background thread
    protected Void doInBackground() throws Exception {
        DoFancyStuff();
        return null;
    }

    // Executed in EDT
    protected void done() {
        try {
            System.out.println("Done");
            get();
        } catch (ExecutionException e) {
            e.getCause().printStackTrace();
            String msg = String.format("Unexpected problem: %s", 
                           e.getCause().toString());
            JOptionPane.showMessageDialog(Utils.getActiveFrame(),
                msg, "Error", JOptionPane.ERROR_MESSAGE, errorIcon);
        } catch (InterruptedException e) {
            // Process e here
        }
    }
}

Вам НЕ следует пытаться перехватывать исключения в фоновом потоке, а разрешать им проходить через сам SwingWorker, а затем вы можете получить их в методе done(), вызвав get(), который обычно возвращает результат doInBackground() (Void в вашей ситуации). Если исключение было сгенерировано в фоновом потоке, то get() сгенерирует его, обернутое внутри ExecutionException.

Обратите также внимание, что overidden SwingWorker методы protected, и вам не нужно делать их public.

13 голосов
/ 29 июня 2011

Один из вариантов - использовать SwingUtilities.invokeLater(...) для публикации действия на EDT

SwingUtilities.invokeLater(new Runnable(){
    @Override
    public void run(){
        JOptionPane.showMessageDialog(
            Utils.getActiveFrame(),
            msg, 
            "Error", 
            JOptionPane.ERROR_MESSAGE,
            errorIcon);
    }
});

И, как вы заметили, SwingWorker - этоспособен сообщать о промежуточных результатах, но вам нужно переопределить process(...), который вызывается при вызове publish(...).

В любом случае, почему бы просто не установить флаг, если возникает исключение, и если этот флаг установлен, показать диалоговое окно в done(), поскольку оно безопасно выполняется в EDT?

1 голос
/ 29 июня 2011

Вы правы, вы нарушаете кардинальное правило Swing, которое не изменяет GUI нигде, кроме как в потоке-событиях-отправителях.

Если бы это был я, я бы выдал событие, которое GUI слушает, чтобы показать сообщение об ошибке. Или вы можете просто обернуть вызов SwingWorker в команду catch и показать там диалог.

0 голосов
/ 17 мая 2018

Прежде всего: извините за короткий ответ, не слишком много времени, чтобы сэкономить.

У меня была такая же проблема: желание опубликовать на System.out изнутри рабочего.

Краткий ответ: Оно не заблокирует ваше приложение, если вы используете метод execute()

Дело в том, что блокировка не выполняется, если вы выполняете рабочий в порядке: фоновая задача.

class MyWorker extend SwingWorker<Void, Void>{
  @Override
  protected Void doInBackground() throws ... {
    // your logic here and a message to a stream
    System.out.println("from my worker, with love");
    // ...
    try { 
      throw new Exception("Whoops, this is an exception from within the worker"); 
    } catch (Exception e) { 
      System.out.println(e.getMessage()); 
    }
  }
}

Теперь вы будете вызывать этого работника, создавая новый экземпляр, а после этого вызывать метод execute(). Но чтобы сэкономить ваше время: вы, вероятно, захотите узнать, когда ваш работник закончил, поэтому вам нужно зарегистрировать прослушиватель изменения свойства, что довольно просто:

class MyListener implements PropertyChangeListener{
  @Override
  public void propertyChange(PropertyChangeEvent evt){
    if(evt.getPropertyName().equals("state") && evt.getNewValue().equals(SwingWorker.StateValue.DONE)){
        System.out.println("The worker is done");
    }
  }
}

И собрать все вместе на вашем main():

public void main(...){
  MyWorker w = new MyWorker();
  MyListener l = new MyListener();
  w.addPropertyChangeListener(l);
  w.execute();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...