Reputation: 2159
Ok, so I've been playing around with SwingWorker a bit and got some simplified code for updating a gui going, but I'm having trouble figuring out how to get the thread to properly terminate when it completes. Currently, it is only terminating via the stop option. How would I set it up to also terminate the thread properly when it finishes its process? Currently, after the return null;
it goes to the package line and hangs.
My code is below:
package concurrency;
import java.util.List;
import java.util.Random;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;
import java.awt.Insets;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class PBTest extends JFrame implements ActionListener {
private final GridBagConstraints constraints;
private final JProgressBar pb, pbF;
private final JButton theButton;
private PBTask pbTask;
private JProgressBar makePB() {
JProgressBar p = new JProgressBar(0,100);
p.setValue(0);
p.setStringPainted(true);
getContentPane().add(p, constraints);
return p;
}
public PBTest() {
super("PBTest");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Make text boxes
getContentPane().setLayout(new GridBagLayout());
constraints = new GridBagConstraints();
constraints.insets = new Insets(3, 10, 3, 10);
pb = makePB();
pbF = makePB();
//Make buttons
theButton = new JButton("Start");
theButton.setActionCommand("Start");
theButton.addActionListener(this);
getContentPane().add(theButton, constraints);
//Display the window.
pack();
setVisible(true);
}
private static class UpdatePB {
private final int pb1, pb2;
UpdatePB(int pb1s, int pb2s) {
this.pb1 = pb1s;
this.pb2 = pb2s;
}
}
private class PBTask extends SwingWorker<Void, UpdatePB> {
@Override
protected Void doInBackground() {
int prog1 = 0;
int prog2 = 0;
Random random = new Random();
while (prog2 < 100) {
if(prog1 >= 100) {
prog1 = 0;
}
//Sleep for up to one second.
try {
Thread.sleep(random.nextInt(1000));
} catch (InterruptedException ignore) {}
//Make random progress.
prog1 += random.nextInt(10);
prog2 += random.nextInt(5);
publish(new UpdatePB(prog1, prog2));
}
return null;
}
@Override
protected void process(List<UpdatePB> pairs) {
UpdatePB pair = pairs.get(pairs.size() - 1);
pb.setValue(pair.pb1);
pbF.setValue(pair.pb2);
}
}
public void actionPerformed(ActionEvent e) {
if ("Start" == e.getActionCommand() && pbTask == null) {
theButton.setText("Stop");
theButton.setActionCommand("Stop");
(pbTask = new PBTask()).execute();
} else if ("Stop" == e.getActionCommand()) {
theButton.setText("Start");
theButton.setActionCommand("Start");
pbTask.cancel(true);
pbTask = null;
} else {
alertMsg("Thread still running.");
}
}
static void alertMsg(String theAlert) {
JOptionPane.showMessageDialog(null, theAlert);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new PBTest();
}
});
}
}
Note: this is basically an alteration of the Java tutorials' "flipper" example... I'm not so much a programmer as a code hacker (/sad face/, lol), at the moment, so I'm kind of at a loss as to where to go next.
Anyway, the code works as intended until it finishes. I tried adding the done()
method, but it never attempts to run it, it always just goes to the package line (when stepping through the debugger) and hangs. Am I supposed to return a value other than null or something?
Thanks in advance for any help!
Upvotes: 1
Views: 1550
Reputation: 4004
I'm not sure what you're trying to achieve. Your example is working fine. Worker thread runs till the end. In case you want to wait till it ends to do something, you have to call method pbTask.get() somewhere in your code. Otherwise, it will just quietly completes without affecting any of your UI components.
Consider the following change to your method to see how it behaves now. Note, that UI freezes because you make UI wait for the thread to complete, but output "DONE" appears in your log only when the WorkerThread completes.
public void actionPerformed(ActionEvent e) {
if ("Start" == e.getActionCommand() && pbTask == null) {
theButton.setText("Stop");
theButton.setActionCommand("Stop");
(pbTask = new PBTask()).execute();
} else if ("Stop" == e.getActionCommand()) {
theButton.setText("Start");
theButton.setActionCommand("Start");
pbTask.cancel(true);
pbTask = null;
} else {
alertMsg("Thread still running.");
}
try {
pbTask.get();
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (ExecutionException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
System.out.println("DONE");
}
This change is just to illustrate the difference. In order to write actual code we need to know more about what you're trying to achieve.
If my extrasensory skills are ok, then you probably want to flip button back to "Start". In order to do this, you need to override done() method in the Worker:
private class PBTask extends SwingWorker<Void, UpdatePB> {
@Override
protected Void doInBackground() {
int prog1 = 0;
int prog2 = 0;
Random random = new Random();
while (prog2 < 100) {
if(prog1 >= 100) {
prog1 = 0;
}
//Sleep for up to one second.
try {
Thread.sleep(random.nextInt(100));
} catch (InterruptedException ignore) {}
//Make random progress.
prog1 += random.nextInt(10);
prog2 += random.nextInt(5);
publish(new UpdatePB(prog1, prog2));
}
return null;
}
@Override
protected void process(List<UpdatePB> pairs) {
UpdatePB pair = pairs.get(pairs.size() - 1);
pb.setValue(pair.pb1);
pbF.setValue(pair.pb2);
}
@Override
protected void done() {
super.done();
theButton.setText("Start");
theButton.setActionCommand("Start");
}
}
Upvotes: 3
Reputation: 109823
for example
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
public class SwingWorkerExample extends JFrame implements ActionListener {
private static final long serialVersionUID = 1L;
private final JButton startButton, stopButton;
private JScrollPane scrollPane = new JScrollPane();
private JList listBox = null;
private DefaultListModel listModel = new DefaultListModel();
private final JProgressBar progressBar;
private mySwingWorker swingWorker;
public SwingWorkerExample() {
super("SwingWorkerExample");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
getContentPane().setLayout(new GridLayout(2, 2));
startButton = makeButton("Start");
stopButton = makeButton("Stop");
stopButton.setEnabled(false);
progressBar = makeProgressBar(0, 99);
listBox = new JList(listModel);
scrollPane.setViewportView(listBox);
getContentPane().add(scrollPane);
//Display the window.
pack();
setVisible(true);
}
//Class SwingWorker<T,V> T - the result type returned by this SwingWorker's doInBackground
//and get methods V - the type used for carrying out intermediate results by this SwingWorker's
//publish and process methods
private class mySwingWorker extends javax.swing.SwingWorker<ArrayList<Integer>, Integer> {
//The first template argument, in this case, ArrayList<Integer>, is what s returned by doInBackground(),
//and by get(). The second template argument, in this case, Integer, is what is published with the
//publish method. It is also the data type which is stored by the java.util.List that is the parameter
//for the process method, which recieves the information published by the publish method.
@Override
protected ArrayList<Integer> doInBackground() {
//Returns items of the type given as the first template argument to the SwingWorker class.
if (javax.swing.SwingUtilities.isEventDispatchThread()) {
System.out.println("javax.swing.SwingUtilities.isEventDispatchThread() returned true.");
}
Integer tmpValue = new Integer(1);
ArrayList<Integer> list = new ArrayList<Integer>();
for (int i = 0; i < 100; i++) {
for (int j = 0; j < 100; j++) { //find every 100th prime, just to make it slower
tmpValue = FindNextPrime(tmpValue.intValue());
//isCancelled() returns true if the cancel() method is invoked on this class. That is the proper way
//to stop this thread. See the actionPerformed method.
if (isCancelled()) {
System.out.println("SwingWorker - isCancelled");
return list;
}
}
//Successive calls to publish are coalesced into a java.util.List, which is what is received by process,
//which in this case, isused to update the JProgressBar. Thus, the values passed to publish range from
//1 to 100.
publish(new Integer(i));
list.add(tmpValue);
}
return list;
}//Note, always use java.util.List here, or it will use the wrong list.
@Override
protected void process(java.util.List<Integer> progressList) {
//This method is processing a java.util.List of items given as successive arguments to the publish method.
//Note that these calls are coalesced into a java.util.List. This list holds items of the type given as the
//second template parameter type to SwingWorker. Note that the get method below has nothing to do with the
//SwingWorker get method; it is the List's get method. This would be a good place to update a progress bar.
if (!javax.swing.SwingUtilities.isEventDispatchThread()) {
System.out.println("javax.swing.SwingUtilities.isEventDispatchThread() + returned false.");
}
Integer percentComplete = progressList.get(progressList.size() - 1);
progressBar.setValue(percentComplete.intValue());
}
@Override
protected void done() {
System.out.println("doInBackground is complete");
if (!javax.swing.SwingUtilities.isEventDispatchThread()) {
System.out.println("javax.swing.SwingUtilities.isEventDispatchThread() + returned false.");
}
try {
//Here, the SwingWorker's get method returns an item of the same type as specified as the first type parameter
//given to the SwingWorker class.
ArrayList<Integer> results = get();
for (Integer i : results) {
listModel.addElement(i.toString());
}
} catch (Exception e) {
System.out.println("Caught an exception: " + e);
}
startButton();
}
boolean IsPrime(int num) { //Checks whether a number is prime
int i;
for (i = 2; i <= num / 2; i++) {
if (num % i == 0) {
return false;
}
}
return true;
}
protected Integer FindNextPrime(int num) { //Returns next prime number from passed arg.
do {
if (num % 2 == 0) {
num++;
} else {
num += 2;
}
} while (!IsPrime(num));
return new Integer(num);
}
}
private JButton makeButton(String caption) {
JButton b = new JButton(caption);
b.setActionCommand(caption);
b.addActionListener(this);
getContentPane().add(b);
return b;
}
private JProgressBar makeProgressBar(int min, int max) {
JProgressBar progressBar1 = new JProgressBar();
progressBar1.setMinimum(min);
progressBar1.setMaximum(max);
progressBar1.setStringPainted(true);
progressBar1.setBorderPainted(true);
getContentPane().add(progressBar1);
return progressBar1;
}
private void startButton() {
startButton.setEnabled(true);
stopButton.setEnabled(false);
System.out.println("SwingWorker - Done");
}
@Override
public void actionPerformed(ActionEvent e) {
if ("Start" == null ? e.getActionCommand() == null : "Start".equals(e.getActionCommand())) {
startButton.setEnabled(false);
stopButton.setEnabled(true);
// Note that it creates a new instance of the SwingWorker-derived class. Never reuse an old one.
(swingWorker = new mySwingWorker()).execute(); // new instance
} else if ("Stop" == null ? e.getActionCommand() == null : "Stop".equals(e.getActionCommand())) {
startButton.setEnabled(true);
stopButton.setEnabled(false);
swingWorker.cancel(true); // causes isCancelled to return true in doInBackground
swingWorker = null;
}
}
public static void main(String[] args) {
// Notice that it kicks it off on the event-dispatching thread, not the main thread.
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
SwingWorkerExample swingWorkerExample = new SwingWorkerExample();
}
});
}
}
Upvotes: 4