0

I'm trying to run a process in the background for three seconds, and have it be cancellable. Right now I have an example that increments an int until three seconds have passed or the worker is cancelled.

import java.util.List;

import javax.swing.SwingWorker;

public class Example {

    public static void main(String[] args) throws InterruptedException {
        ExampleWorker worker = new ExampleWorker();
        worker.execute();
        Thread.sleep(10);
        //worker.cancel(false);
    }

}

class ExampleWorker extends SwingWorker<String, Integer> {

    private int n = 0;
    private long startTime;
    private boolean cancelled = false;

    @Override
    protected String doInBackground() throws Exception {
        System.out.println("doInBackground()");
        startTime = System.currentTimeMillis();
        while (!cancelled && System.currentTimeMillis() - startTime < 3000) {
            publish(n++);
        }
        return "What is this used for?";
    }

    @Override
    protected void done() {
        cancelled = true;
        System.out.println(n);
    }

}

I used to have a process method that printed out n. I removed it, but I kept publish(n++). Everything worked. But when I replace publish(n++) with n++, nothing gets printed out if I don't cancel the worker. Why does removing publish create this problem?

Tilded
  • 421
  • 3
  • 11
  • Thread safety bugs prevent the worker from seeing the variable change, make the variable atomic – Ferrybig Feb 25 '16 at 19:29
  • Where's your process method? Publish will queue items that are passed to process for you to process, process is called on the EDT making it safe to update the UI from. This is why you use publish and process together. There no guarantee that process will be called, so you might need to include a Thread.yield after each call to publish – MadProgrammer Feb 25 '16 at 19:56

1 Answers1

1

You should have a look at Worker Threads and SwingWorker and the JavaDocs for SwingWorker which will give you more information.

Basically, publish places the values onto a queue which will be passed to the publish method within the context of the EDT. This is used to allow information to be passed from the background thread of the worker to the EDT where the values can be used to update the UI safely.

The done method should be used to get the result of the background operation (assuming it has one), which can retrieved by using the get method which will return the value which was returned from the doInBackground method or the exception which was generated

import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.SwingWorker;

public class Test {

    public static void main(String[] args) throws InterruptedException {
        ExampleWorker worker = new ExampleWorker();
        worker.execute();
        Thread.sleep(100);
        try {
            System.out.println(">> " + worker.get());
        } catch (ExecutionException ex) {
            ex.printStackTrace();
        }
    }

    public static class ExampleWorker extends SwingWorker<String, Integer> {

        private int n = 0;
        private long startTime;

        @Override
        protected String doInBackground() throws Exception {
            System.out.println("doInBackground()");
            startTime = System.currentTimeMillis();
            while (System.currentTimeMillis() - startTime < 3000) {
                publish(n++);
                Thread.yield();
            }
            return "What is this used for?";
        }

        @Override
        protected void process(List<Integer> chunks) {
            for (int value : chunks) {
                System.out.println(value);
            }
        }

        @Override
        protected void done() {
            System.out.println("Done: " + n);
        }

    }

}

Updated with a more directed example...

I don't have a process method because I don't want to pass information to the EDT until the background operation finishes.

Then don't use publish/process, use done and return the value you want to be retrieved from doInBackground

The only reason I still have publish in my code is because it doesn't work without it, if I don't cancel the process.

Seems to work okay for me, and SwingWorker already has it owns cancel state

And doesn't get() block everything on the EDT? I don't want that because it prevents my GUI from working

Yes, while the worker is running, it will return immediately if the worker has completed. In your example, with the absence of any UI to keep the JVM from exiting, it was a way to keep the JVM from exiting

This means you can use the done which is normally called after doInBackground returns and/or within a PropertyChangeListener, depending on your needs

import java.awt.EventQueue;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

    public static void main(String[] args) throws InterruptedException {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new JLabel("Waiting"));
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);

                ExampleWorker worker = new ExampleWorker();
                worker.addPropertyChangeListener(new PropertyChangeListener() {
                    @Override
                    public void propertyChange(PropertyChangeEvent evt) {
                        if ("state".equals(evt.getPropertyName())) {
                            ExampleWorker worker = (ExampleWorker) evt.getSource();
                            switch (worker.getState()) {
                                case DONE: {
                                    try {
                                        System.out.println("PropertyChange: " + worker.get());
                                    } catch (InterruptedException | ExecutionException ex) {
                                        ex.printStackTrace();
                                    }
                                    frame.dispose();
                                }
                                break;
                            }
                        }
                    }
                });
                worker.execute();
            }
        });
    }

    public static class ExampleWorker extends SwingWorker<Integer, Integer> {

        private int n = 0;
        private long startTime;

        @Override
        protected Integer doInBackground() throws Exception {
            System.out.println("doInBackground()");
            startTime = System.currentTimeMillis();
            while (System.currentTimeMillis() - startTime < 3000) {
                n++;
                Thread.yield();
            }
            return n;
        }

        @Override
        protected void process(List<Integer> chunks) {
            for (int value : chunks) {
                System.out.println(value);
            }
        }

        @Override
        protected void done() {
            try {
                System.out.println("Done: " + get());
            } catch (InterruptedException | ExecutionException ex) {
                ex.printStackTrace();
            }
        }

    }

}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • I don't have a process method because I don't want to pass information to the EDT until the background operation finishes. The only reason I still have publish in my code is because it doesn't work without it, if I don't cancel the process. And doesn't get() block everything on the EDT? I don't want that because it prevents my GUI from working. – Tilded Feb 25 '16 at 20:37
  • If you don't want to update the UI until the Worker is finished then use the `get` method when `done` is called, which will get the value which `doInBackground` returns. `get` will only block while the worker is running, this is why it's safe to use in the `done` method, I included it simply for demonstration purpose, because I had no context to the overall problem you were trying to resolve – MadProgrammer Feb 25 '16 at 20:42
  • OK, I think I figured it out. I used `get` in `done` as you suggested, which let me take out `publish`. Then to deal with canceling, I caught the `CancellationException` in `done` and was able to print out `n` from there. Thanks! – Tilded Feb 25 '16 at 20:57
  • Actually, my previous comment was wrong. I think it must be something wrong with my computer or some overflow error failing silently? It doesn't work if I have the process stop after three seconds, but it does work if I have is stop after 1 second. – Tilded Feb 25 '16 at 21:13
  • I'm still unaware of what you are ultimatly trying to achieve, so it's difficult to make suggestions – MadProgrammer Feb 25 '16 at 21:26
  • The application for this is to make a Monte Carlo AI that runs for a few seconds without blocking the GUI, but can also be cancelled anytime. The problem might actually be that the [JVM is shutting down](http://stackoverflow.com/questions/14900697/why-does-swingworker-stop-unexpectedly?rq=1). I'm in the process of trying the code within my project to see if it works there. – Tilded Feb 25 '16 at 21:38