27

I'm trying to find out how to stop a SwingWorker thread from running when I press a button. I have been looking around and I'm having some trouble working out how to do this. At the moment this is what I have:

new MySwingWorkerClass(args).execute();

I'm then creating a button which I want to use in order to stop the thread:

button = new JButton("Stop");
button.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) 
        {
        // Stop the swing worker thread
        }
});

I have already looked around in search of an answer, so far I have managed to find the cancel method. I don't understand how to use this to stop my swing worker though. I tried the following but it didn't work:

SwingWorker.cancel(true);
mre
  • 43,520
  • 33
  • 120
  • 170
  • 1
    cancel is _not_ a static method, so the last line doesn't even compile .. keep a reference to the instance (as @Jonas suggested) it the way to go – kleopatra Feb 13 '12 at 23:55

4 Answers4

45

You need to keep a reference to your SwingWorker, then you use that reference to cancel the worker thread.

MySwingWorker myWorker = new MySwingWorkerClass(args).execute();

button.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) 
    {
        // Stop the swing worker thread
        myWorker.cancel(true);
    }
});

Here is a full example:

enter image description here

public class WorkerDemo extends JFrame {
    private boolean isStarted = false;
    private JLabel counterLabel = new JLabel("Not started");
    private Worker worker = new Worker();
    private JButton startButton = new JButton(new AbstractAction("Start") {

        @Override
        public void actionPerformed(ActionEvent arg0) {
            if(!isStarted) { 
                worker.execute();
                isStarted = false;
            }
        }

    });
    private JButton stopButton = new JButton(new AbstractAction("Stop") {

        @Override
        public void actionPerformed(ActionEvent arg0) {
            worker.cancel(true);
        }

    });

    public WorkerDemo() {

        add(startButton, BorderLayout.WEST);
        add(counterLabel, BorderLayout.CENTER);
        add(stopButton, BorderLayout.EAST);
        pack();
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setVisible(true);
    }

    class Worker extends SwingWorker<Void, Integer> {

        int counter = 0;

        @Override
        protected Void doInBackground() throws Exception {
            while(true) {
                counter++;
                publish(counter);
                Thread.sleep(60);
            }
        }

        @Override
        protected void process(List<Integer> chunk) {

            // get last result
            Integer counterChunk = chunk.get(chunk.size()-1);

            counterLabel.setText(counterChunk.toString());
        }

    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                new WorkerDemo();
            }

        });
    }

}
Jonas
  • 121,568
  • 97
  • 310
  • 388
  • 2
    I'm curious as to why this answer hasn't been accepted. It seems like the correct and most obvious one. – eternaln00b Feb 11 '12 at 22:38
  • 1
    just noticed a little typo: in startButton, you probably want to set the isStarted property to true :-) – kleopatra Mar 05 '12 at 11:30
  • 2
    actually, it doesn't make a difference: api doc states _SwingWorker is only designed to be executed once. Executing a SwingWorker more than once will not result in invoking the doInBackground method twice_ – kleopatra Mar 05 '12 at 11:37
  • does swingworker.cancel(true) ensure that the swingworker is cancelled/killed? if I put a while loop in there and try to cancel the worker from main class, the loop doesn't stop – yolob 21 Jul 17 '19 at 15:43
16

You need to periodically check its cancelled flag (i.e. isCancelled()). SwingWorker leaves how to handle the interrupt up to you.

For more information, see Cancelling Background Tasks.

mre
  • 43,520
  • 33
  • 120
  • 170
  • 1
    example for `isCancelled()` http://stackoverflow.com/questions/6113944/how-cancel-the-execution-of-a-swingworker/6114890#6114890 , +1 – mKorbel Nov 10 '11 at 18:31
  • @mre thankyou for your help. Unfortunately I'm still confused as to the best approach since it's not possible to regularly check the status of isCancelled in my code. Is there a way I can just terminate it completely? –  Nov 15 '11 at 16:45
  • I would post it, however there a lots of classes. Basically, I call a method that runs some code in a loop, this can take several hours to run since there's a lot of processing of data to be done. This is exactly the reason I need a stop button to somehow kill the thread or interrupt it or something. –  Feb 07 '12 at 17:50
  • @TheCrazyChimp, You need to intermittently check the canceled flag, otherwise you'll never know that you've been requested to stop. – mre Feb 09 '12 at 01:59
  • @TheCrazyChimp "I call a method that runs some code in a loop". You can do one of two things: 1) Call `isCancelled()` somewhere in that loop (you might need to do some refactoring so that your method which does the looping has access to the SwingWorker instance). 2) Call `Thread.sleep()` somewhere in that loop. (This works because `Thread.sleep()` checks whether the current thread has been cancelled.) – vaughandroid Feb 14 '12 at 15:38
  • 1
    Thank you for this link - the `while(!isCancelled()) {...}`-Loop helped me alot! – Jochen Birkle Feb 20 '14 at 06:15
2

I think you have to restart the counter after every stop.

For startButton, you have to have worker restarted as in

private JButton startButton = new JButton(new AbstractAction("Start") {

        @Override
        public void actionPerformed(ActionEvent arg0) {
            if(!isStarted) { 
                worker = new Worker();     // New line
                worker.execute();
                isStarted = false;
            }
        }
    });

To stop you can use

worker.setStopFlag(); // in stopButton actionPerformed block.

in Worker class private boolean stopFlag = false;

and add

if( stopFlag)
break;

after Thread.sleep(60); and finally put setter for stopFlag at the end of Worker class as

void setStopFlag(){
    stopFlag = true;
}

By the way, you can use cancel(true) if you want to have exception exit.

Hatto
  • 65
  • 2
2

you can try overidding the done() method and put it in try catch and catch the java.util.concurrent.CancellationException. Something like

. . .

@Overide
done() {
   try {
     get();
   } catch (CancellationException e) {
       // Do your task after cancellation
   }
}

. . .

rahul pasricha
  • 931
  • 1
  • 14
  • 35
  • It is generally considered bad practice to use Exceptions for program flow. ActionListeners would probably be a better solution. – element11 Apr 17 '15 at 13:31