Nick Fanelli
Nick Fanelli

Reputation: 284

Background SwingWorker thread not executing a particular piece of code

I couldn't find a better way to express the problem in the title of the question, but hopefully you'll understand...

I have the following code...

[...]
final JDialog waitingDialog = optionPane.createDialog(optionPane, "Processing in backgroung");

waitingDialog.setLocationRelativeTo(homeFrame);
waitingDialog.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);


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

    @Override
    protected Void doInBackground() throws Exception {
        // Background processing is done here
        System.out.println("[DEBUG] -- Background processing has started");
        MyCustomClass.initParameters();
        System.out.println("DEBUG] -- initParameters() has finished. Starting main processing now...");
        waitingDialog.setVisible(true);
        MyCustomClass.run();
        return null;
    }

    // This is called when background thread above has completed
    @Override
    protected void done() {
        waitingDialog.dispose();
    };
};
backgroundThread.execute();

[and does other things after this...]

... which executes just fine until it hits MyCustomClass.run(). When it is supposed to start such method, it simply doesn't do it. The application doesn't pause, stop or crash... it just keeps on waiting to continue after the background processing is done, which is not happening for some unknown reason.

MyCustomClass.run() is a static method over at MyCustomClass. I suspected the problem could be with its name - run - so I changed it to some random thing like "doIt()", but the issue persisted. The very first line on the method is a simple System.out.println("I got here"), but not even that gets printed. I added breakpoints just before and right after the method invocation and tried debugging, but that didn't help either. Everything seemed fine and I couldn't find out the problem.


Edit
I ask: how do I make the dialog show only when I wanted it to show? In other words, the script is:

  1. show the "waiting" dialog when SwingWorker is started
  2. hide/stop/disable/dispose the dialog when the initParameter() issues the option dialog that needs user input (press of a button);
  3. show the waiting dialog again as the processing resumed
  4. dispose of the dialog because the background processing has completed

Upvotes: 1

Views: 2052

Answers (3)

Nick Fanelli
Nick Fanelli

Reputation: 284

First things first, I accepted @Hovercraft Full Of Eels answer because it indeed helped me. Like I said in the comments "Simply replacing the setVisible(true) from within the SwingWorker to right after its definition made the program flow as expected", which was done thanks to his explanation.

However, I needed a little more (which is described as a script in the "Edit" section of the original question). I managed to achieve the desired behavior with the following code...

final JOptionPane optionPane = new JOptionPane("<html>Please wait...<br>Initializing parameters</html>", JOptionPane.INFORMATION_MESSAGE, JOptionPane.DEFAULT_OPTION,
                    new ImageIcon(Home.class.getResource("/path/to/a/gif/loading.gif")), new Object[]{}, null);

            final JDialog waitingDialog = optionPane.createDialog(optionPane, "MyApp");
            waitingDialog.setLocationRelativeTo(homeFrame);
            waitingDialog.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);


            SwingWorker<Void, Void> backgroundThread1 = new SwingWorker<Void, Void>() {
                @Override
                protected Void doInBackground() throws Exception {
                    System.out.println("[DEBUG] -- Background processing of initParameters() has started");
                    MyCustomClass.initParameters();
                    System.out.println("[DEBUG] -- initParameters() has finished. Moving on...");
                    return null;
                }

                @Override
                protected void done() {
                    waitingDialog.setVisible(false);
                    optionPane.setMessage("<html>Please wait... <br>Now working on your thing.</html>");
                };
            };
            backgroundThread1.execute();
            waitingDialog.setVisible(true);

            SwingWorker<Void, Void> backgroundThread2 = new SwingWorker<Void, Void>() {
                @Override
                protected Void doInBackground() throws Exception {
                    System.out.println("[DEBUG] -- Start now the main processing in background");
                    MyCustomClass.run();
                    return null;
                }

                @Override
                protected void done() {
                    waitingDialog.dispose();
                };
            };
            backgroundThread2.execute();
            waitingDialog.setVisible(true);

Basically (and pretty obviously) all I did was create a new SwingWorker and have each of the routines run in one of them. When the first one is done, all it does is hide (setVisible(false)) the "waitingDialog" - so the main thread isn't blocked on it - and changes the JOptionPane's message to be used by the dialog when it is made visible again after the second SwingWorker has started.

I don't know whether this is a good or even best / most clever way to do it, but it is working. If there is a better way, I would be very glad to learn about it.

There is only one issue which I haven't been able to solve yet. Somewhere and somehow during the MyCustomClass.run() routine, the second SwingWorker, which is the one executing it, is being killed/terminated. How I know it is happening? Well, for other debugging reasons, there is a loop in my routine printing a certain value as it is updated and then all of a sudden it stops printing (no, the loop hasn't reached its end), the "waitingDialog" disappears and nothing else happens. The app doesn't crash or anything. I am able to go about using it as if nothing had happened. Not a single exception is thrown.

I don't know how or why this is happening. Any help would be greatly appreciated.

Upvotes: 2

DaveB
DaveB

Reputation: 2143

Swingworker is known to swallow runtime exceptions in the background thread. You can detect them by performing a "get()" in the done() method of the Swingworker. Do a search for "SimpleSwingWorker" and you will find examples of how to do this.

Upvotes: 1

Hovercraft Full Of Eels
Hovercraft Full Of Eels

Reputation: 285405

Immediately off the bat I see that you're making Swing calls from within the SwingWorker's doInBackground, something which completely defeats the purpose of using a SwingWorker or background thread. Also, the call you're making, one that displays a modal dialog:

waitingDialog.setVisible(true); // **** here ****
MyCustomClass.run();   // **** not sure what this does or if this makes Swing calls or not.

will pause all code flow below it until the dialog is no longer visible, as that is how modal dialogs function. This is what is blocking your SwingWorker. You must try to separate your code so that Swing calls are only made on the Swing event thread, and use the worker for only the background work.

Instead

  • Create your SwingWorker
  • Execute your SwingWorker
  • Then call setVisible(true) on your modal dialog

In pseudocode:

create modal dialog
create SwingWorker
Attach any PropertyChangeListeners to the SwingWorker (if needed)
Execute the SwingWorker
Display the modal dialog

So, the first quick change I would make in your code above would be to remove waitingDialog.setVisible(true); from your SwingWorker doInBackground() method, and then call the waitingDialog.setVisible(true); method AFTER executing the SwingWorker, i.e., after backgroundThread.execute(); in particular. I know it seems counter-intuitive to call dispose on it seemingly before making it visible, but trust me, it will work better this way, since the dispose call will be blocked by the time it takes to run the background thread.

Upvotes: 5

Related Questions