26

I am using threading in application through Swing Worker class. It works fine, yet I have a bad feeling about showing an error message dialog in try-catch block. Can it potentially block the application? This is what it looks right now:

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

Can it be done in a safe way using Swing Worker framework? Is overriding publish() method a good lead here?

EDIT:

Did it like this:

} catch (final Exception e) {

    SwingUtilities.invokeLater(new Runnable() {

        public void run() {

            e.printStackTrace();

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

            JOptionPane.showMessageDialog(Utils
                    .getActiveFrame(), msg, "Error",
                    JOptionPane.ERROR_MESSAGE, errorIcon);

        }
    });

}

Calling get in done method would result in two try-catch blocks, as the computational part throws exceptions, so I think this is cleaner in the end.

APerson
  • 8,140
  • 8
  • 35
  • 49
fbielejec
  • 3,492
  • 4
  • 27
  • 35

4 Answers4

65

The right way to do it is as follows:

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
        }
    }
}

You should NOT try to catch exceptions in the background thread but rather let them pass through to the SwingWorker itself, and then you can get them in the done() method by calling get()which normally returns the result of doInBackground() (Voidin your situation). If an exceptionwas thrown in the background thread then get() will throw it, wrapped inside an ExecutionException.

Please also note that overidden SwingWorker methods are protected and you don't need to make them public.

APerson
  • 8,140
  • 8
  • 35
  • 49
jfpoilpret
  • 10,449
  • 2
  • 28
  • 32
  • that's about try - catch - finally block inside nested classes created SwingWorkers methods/funcionality(ies) :-) +1 – mKorbel Jun 30 '11 at 06:22
  • this works too, as far as I tested, however in my case adds a lot of code. The computational part throws Exceptions so I would have to add two blocks of try-catch. Sth to keep in mind though. – fbielejec Jun 30 '11 at 06:52
  • 2
    Then in this case, we may wonder why you are using `SwingWorker` and not use your own thread for the background work, since you're not using any relevant `SwingWorker` feature. Please also note that your code DOES NOT handle interruptions of the background thread. – jfpoilpret Jul 01 '11 at 05:28
13

One option is to use SwingUtilities.invokeLater(...) to post the action on the EDT

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

And as you noted, SwingWorker is capable of reporting intermediate results, but you'll need to override process(...), which is called when you invoke publish(...).

Regardless, why not just set a flag if an exception occurs, and if that flag is set, show the dialog in done() since it's executed safely in the EDT?

mre
  • 43,520
  • 33
  • 120
  • 170
  • 4
    Handling it in the done() method is exactly how I have approached this situation in the past. invokeLater is a great solution as well. – jzd Jun 29 '11 at 16:28
  • 1
    1) I never ever to allowed runs any of code that's could throws Exception inside SwingWorker 2) PropertyChangeListener would be catch any of exeption from SwingWorker 3) all looks like as building big trouble going this way :-) – mKorbel Jun 29 '11 at 16:45
  • @little bunny foo foo, not exactly, just call unsafe code packed in separated void, class or whatever ..., not wrap that inside SwingWorker method(s) – mKorbel Jun 29 '11 at 16:55
  • @mKorbel I don't see why you wouldn't allow code that throws exceptions inside `SwingWorker`; see my answer: `SwingWorker` is perfectly equipped to handle that case (and directly in EDT without extra need for `SwingUtilities.invokeLater()`). – jfpoilpret Jun 29 '11 at 20:53
  • This works, I had to change modifier of an Exception being caucht to final though. I think this is by far the clearest way to code it. – fbielejec Jun 30 '11 at 06:48
  • Using `SwingUtilities.invoke...` along with ^SwingWorker` is probably the worst way to code. `SwingWorker` was invented precisely to avoid explicit use of ìnvoke `SwingUtilities.invoke...`. You have to make up your mind: in this particular code, either you use `SwingUtilities` without `SwingWorker` or the opposite way, but certainly not a mix of both. – jfpoilpret Jul 01 '11 at 10:44
  • @little bunny foo foo: what's your problem? I didn't insult anyone with my comment. I just expressed an opinion in my comment, which I believe many Swing developers would agree with. I considered your response a bad advice to someone working with Swing. Please note that I didn't vote it down (in case you wonder), simply because it seemed to provide an answer to the OP. – jfpoilpret Jul 05 '11 at 21:08
1

You are right, you are violating the cardinal rule of Swing, which is don't modify the GUI anywhere except for on the event-dispatch-thread.

If it was me, I would throw an event that the GUI listens for to show the error message. Or, you can just wrap the invocation of the SwingWorker in a try catch and show the dialogue there.

ban-geoengineering
  • 18,324
  • 27
  • 171
  • 253
hvgotcodes
  • 118,147
  • 33
  • 203
  • 236
0

First of all: sorry for the short answer, don't have too much time to spare.

I had the same problem: wanting to publish to System.out from within the worker.

Short answer: It won't block your app if you use the execute() method

The thing is that there is no blocking if you execute the worker as it should be: a background task.

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

Now you will invoke this worker creating a new instance, and after that calling the execute() method. But to save you some time: you will probably want to know when your worker is done, so you'll need to register an property change listener, which is fairly simple:

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

And to put everything together at your main():

public void main(...){
  MyWorker w = new MyWorker();
  MyListener l = new MyListener();
  w.addPropertyChangeListener(l);
  w.execute();
}