问题
I have problem with handling threads in my application. It creates JFrame and starts a new Thread. Last one will execute external application and update GUI. Then
I have problem to make Main class to wait for second thread to finish, but also to update GUI simultaneously.
Here's my example (shortened):
class Main {
public int status;
public Main() {
// Creating GUI etc.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JDialog id = new JDialog();
id.button.addMouseListener(new MouseListener()); // Calls generate() method
}
});
}
public void generate() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// Make changes to GUI
}
});
GeneratorThread genTest = new GeneratorThread(this, 1, 1, 1);
genTest.start();
//while (status == 0);
System.out.println("Next step.");
}
}
And Thread class:
public class GeneratorThread extends Thread {
protected Main main;
protected int setSize, minValue, maxValue;
public GeneratorThread(Main main, int setSize, int minValue, int maxValue) {
this.main = main;
this.setSize = setSize;
this.minValue = minValue;
this.maxValue = maxValue;
}
public void run() {
// Execute program etc.
// Change GUI from main in the same time
// About 3 seconds
main.status = 1;
}
}
I'm in progress and I wanted to check how it works so far. While worked nice, but it locks Swing somehow and any changes are visible only when GeneratorThread
finishes. I would like to update GUI in the real time.
I've tried join()
, effects are the same. I also tried wait()
(on Main
), but then I got IllegalStateMonitorException.
Any hints?
回答1:
Swing is a single threaded environment. That is, there is a single thread responsible for managing all the interactions and updates to the Swing UI - the Event Dispatching Thread.
Among the golden rules of Swing are...
- DON'T block the EDT (
Thread.sleep
,Thread#join
,Object#wait
, block IO and/or time consuming tasks (among others) should never be called from within the EDT), doing so will stop the EDT from dispatching events and paint updates (amongst other things) - ONLY create/update Swing UI elements from within the EDT.
This raises a question...how do you "wait" for a thread?
The best way is use an Observer pattern. Basically, you provide the Thread
with some kind of reference that it will call to provide notification of events, such as errors and completion...
This will require you to think very carefully about the design of your applications, as you can not rely on a simple A to B execution of your code.
For example...
public class TestThreadCallBack {
public static void main(String[] args) {
new TestThreadCallBack();
}
public TestThreadCallBack() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public interface ThreadCallBack {
public void threadCompleted(Runnable source);
public void threadFailed(Runnable source);
}
public class TestPane extends JPanel implements ThreadCallBack {
private JLabel message;
private JLabel dots;
private int count;
private Timer timer;
public TestPane() {
setLayout(new GridBagLayout());
message = new JLabel("Running background task, please wait");
dots = new JLabel(" ");
add(message);
add(dots);
timer = new Timer(250, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
count++;
if (count > 3) {
count = 0;
}
StringBuilder sb = new StringBuilder(3);
for (int index = 0; index < count; index++) {
sb.append(".");
}
for (int index = count; index < 3; index++) {
sb.append(" ");
}
dots.setText(sb.toString());
}
});
timer.setRepeats(true);
timer.setCoalesce(true);
timer.start();
Thread thread = new Thread(new BackgroundTask(this));
thread.start();
}
@Override
public void threadCompleted(Runnable source) {
timer.stop();
message.setText("Task completed successfully");
}
@Override
public void threadFailed(Runnable source) {
timer.stop();
message.setText("Task failed");
}
}
public class BackgroundTask implements Runnable {
private ThreadCallBack callBack;
public BackgroundTask(ThreadCallBack callBack) {
this.callBack = callBack;
}
@Override
public void run() {
System.out.println("Background task underway...");
try {
Thread.sleep(2000);
} catch (InterruptedException interruptedException) {
}
int result = (int) Math.round((Math.random() * 1));
if (result == 0) {
callBack.threadCompleted(this);
} else {
callBack.threadFailed(this);
}
}
}
}
Updating the UI from within a Thread
other then the EDT is, well, messy. An easier solution would actually be to use a SwingWorker. This has publish/process methods that make easy to update the UI and progress methods that can be used to provide feedback about the progress of the current task.
You can use it's done
method to notify interested parties when the worker has completed.
回答2:
Update your GUI from within the thread using SwingUtilitied.invokeLater or, alternatively, synchronise the main variable!
http://www.vogella.com/articles/JavaConcurrency/article.html#concurrencyjava
Maybe it already suffices to make "status" volatile?
来源:https://stackoverflow.com/questions/14221617/waiting-for-thread-while-updating-swing