How Cancel the Execution of a Swingworker

How cancel the execution of a SwingWorker?

By default, SwingWorker reuses worker threads, so it is perfectly normal that, even though, doInBackground() has returned, to still see the thread that executed your method.

You can identify this fact by looking at the thread names, as reported by NetBeans: SwingWorker-pool-1-thread-1, where that pool is managed by SwingWorker.

If you want more control, you can also pass the SwingWorker instance to an Executor.

Just check SwingWorker and Executor javadoc for further information.

Besides, SwingWorker.cancel() is not supposed to be called from doInBackground() but rather from another thread, generally the EDT, e.g. when user clicks a Cancel button in a progress dialog.

Stop/cancel SwingWorker thread?

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:

Sample Image

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

});
}

}

SwingWorker, Cancel-Button doesn't work

Maybe if you use while instead of if on doInBackground() method of Worker class you will solve your problem. You must to put out of the while loop the mapp(), because you only want to invoke it one time. You should do something like this:

class Worker extends SwingWorker<Void, Void> {

@Override
protected Void doInBackground() throws Exception {

mapp();
while(!isCancelled()){
Thread.sleep(60);
}
System.out.println("SwingWorker - isCancelled");
return null;
}

This link could be useful to understanding how to use SwingWorker.

EDIT:

As you can see on another questions like this or this, using SwingWorker has some problems to manage the cancel method, because this method Attempts to cancel execution of this task. This attempt will fail if the task has already completed, has already been cancelled, or could not be cancelled for some other reason, like Oracle explains, and those "some other reasons" are discussed on the links I've posted.

You can do solve your problem using directly Threads. Your code would be something like this:

    public class MainWindow extends JFrame implements ActionListener, WindowListener
{
// Some code, like generating JFrame, JButtons and other stuff not affencting the task.

final Thread th1 = new Thread(new Runnable() {

@Override
public void run() {
mapp();

}
});
public void actionPerformed(ActionEvent e)
{
boolean isStarted = false;

// Start Button
if (e.getSource() == this.buttonStart)
{
if(!isStarted)
{
System.out.println("start");
labelSuccess.setText("Mapping started!");
this.setEnabled(true);
th1.start();
isStarted = false;
}
}
// Stop Button
if (e.getSource() == this.buttonStop)
{
labelSuccess.setText("Mapping stopped!");
th1.stop();
}
}

This solutions uses the method stop(), which is deprecated, but it works. I've tried using interrupt(), but I don't know why the thread ran till finish the execution of mapp(). Obviously, using stop() is not the best method but it works stopping the mapp() execution before it finishes.

I recommend you to learn more about SwingWorker, Thread and Task to find the best solution to your problem.

Stop SwingWorker with stop button

Start by creating an instance field to which you can maintain a reference to the SwingWorker...

public class rungui extends JFrame {

private SwingWorker<String, Void> worker;
//..

Change your start method to assign an instance to it...

void start() {
// Check to see if the worker is already running...
if (worker == null || worker.isDone() || worker.isCancelled()) {
worker = new SwingWorker<String, Void>() {

@Override
protected String doInBackground() throws Exception {
//...
}

protected void done() {
worker = null;
//...
}

};
//...

Then, you need to monitor the isCancelled state of the SwingWorker.

It's important that you check before you do any "significant" work, as some blocking functionality may not be interruptible

@Override
protected String doInBackground() throws Exception {
useragentwa = new read();
userAgent = useragentwa.close();
Document page = null;

boolean shouldContinue = !isCancelled() || test;
while (shouldContinue) {

if (isCancelled()) {
shouldContinue = false;
continue;
}

finalDomain = http + "" + start_index + "" + domain;
check = start_index % 20;
if (check == 0) {
useragentwa = new read();
userAgent = useragentwa.close();
System.out.println(userAgent);
}
if (isCancelled()) {
shouldContinue = false;
continue;
}
start_index++;
System.out.println(start_index);
try {
page = Jsoup.connect(finalDomain).userAgent(userAgent).timeout(10 * 1000).get();
} catch (Exception e) {
start_index--;
continue;
}
if (isCancelled()) {
shouldContinue = false;
continue;
}
if (page.title().contains("U.S. Directory - Online Yellow Pages")) {

// area may want to append in console text area
continue;

} else {
System.out.println("found something : " + finalDomain);
test = false;
shouldContinue = false;
continue;
}

}

return finalDomain;
}

And finally, you need to call cancel on the instance of the SwingWorker...

    stop_button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if(worker != null && !worker.isCancelled() && !worker.isDone()) {
worker.cancel(true);
}
}
});

To be honest, this is just programing 101

How to Stop a SwingWorker?

This is not a answer to the question (which is how to cancel, I think that's solved) but how to separate the background handling from the view/model updates. A pseudo-code outline:

public static class MyDBWorker extends SwingWorker<Void, Object[]> {

private JTable table;
private DefaultTableModel model;
private ResultSet resultado;

public MyDBWorker(JTable table, ResultSet resultado) {
this.table = table;
this.resultado = resultado;
}

@Override
protected Void doInBackground() throws Exception {
ResultSetMetaData metadata = resultado.getMetaData();
int columnas = metadata.getColumnCount();
Object[] etiquetas = new Object[columnas];
for (int i = 0; i < columnas; i++) {
etiquetas[i] = metadata.getColumnName(i + 1);
}
publish(etiquetas);
while (resultado.next() && !this.isCancelled()) {
Object fila[] = new Object[columnas];
for (int i = 0; i < columnas; i++) {
fila[i] = resultado.getObject(i + 1);
}
publish(fila);
}
return null;
}

@Override
protected void process(List<Object[]> chunks) {
int startIndex = 0;
// first chunk, set up a new model
if (model == null) {
model = new DefaultTableModel();
model.setColumnIdentifiers(chunks.get(0));
table.setModel(model);
startIndex = 1;
}
for (int i = startIndex; i < chunks.size(); i++) {
model.addRow(chunks.get(i));
}
}

@Override
protected void done() {
// nothing to do, we were cancelled
if (isCancelled()) return;
// check for errors thrown in doInBackground
try {
get();
// all was well in the background thread
// check if there are any results
if (table.getModel().getRowCount() == 0) {
// show message
}
} catch (ExecutionException e) {
// we get here if f.i. an SQLException is thrown
// handle it as appropriate, f.i. inform user/logging system
} catch (InterruptedException e) {
//
}
}

}

// use it
SwingWorker worker = new MyDBWorker(table, resultado);
PropertyChangeListener stateListener = new PropertyChangeListener() {

@Override
public void propertyChange(PropertyChangeEvent e) {
if (e.getNewValue() == SwingWorker.StateValue.STARTED) {
progressBar.setIndeterminate(true);
}
if (e.getNewValue() == SwingWorker.StateValue.DONE) {
progressBar.setIndeterminate(false);
}

}

};
worker.addPropertyChangeListener(doneListener);
worker.execute();

Edit

fixed a bug in process: adding rows to the model must start on first index of the batch always, except when processing the header.

Cancel SwingWorker clearly with session

public void actionPerformed(ActionEvent arg0) {
if(psw != null && !psw.isDone()){
closeDBConnection(psw.getSession());
psw.cancel(true);
psw = null;
restoreData();
}

This sure looks to me like you're closing the db connection while the reader thread is still running, which would probably result in exactly what you're seeing. You need to restructure things so the reader thread responds to the cancel and closes the connection itself in a finally block.

SwingWorker: error thrown when done() is called after worker thread is cancelled

Your solution is imperfect since cancellation could occur between the time you call isCancelled() and the time you call get() which would, rarely, still result in the exception. Better would be to catch the CancellationException and handle it.



Related Topics



Leave a reply



Submit