问题
I have some Java Swing login panel. When user click "login" button, extensive DB communication takes place. I got an idea to put that db communication into another thread (that db communication is used for filling some hash maps and singletons). After putting that communication into separate thread, db heavy lifting takes place while user is typing it's uname and password. When user clicks "login" button, than code will wait for "heavy lifting" thread to join, and then proceed.
The problem is that while my code is waiting for db thread to join, it seems that no UI updates can be done. I don't think i can use SwingWorker for db communication because user can click "login" button any time, even before SW would finish and i don't have a way to join SwingWorker (db communication has to take place BEFORE actual login).
Is there a way to enable Swing ui updates while waiting for another thread to join? Am i missing something?
回答1:
Simply put two flags and at the end of both operations (DB-communication and user click), call the same method and verify there the state of the two flags. Possibly show a progress bar in a modal dialog if you want to block user input:
import java.awt.BorderLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.UnsupportedLookAndFeelException;
public class TestThreadJoin {
private boolean dbWorkDone = false;
private boolean credentialsProvided = false;
private JTextField loginTF;
private JTextField passwordTF;
protected void initUI() {
final JFrame frame = new JFrame(TestThreadJoin.class.getSimpleName());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel(new GridBagLayout());
JLabel login = new JLabel("Login: ");
JLabel password = new JLabel("Password: ");
loginTF = new JTextField(20);
passwordTF = new JPasswordField(20);
GridBagConstraints gbc = new GridBagConstraints();
panel.add(login, gbc);
gbc.gridwidth = GridBagConstraints.REMAINDER;
panel.add(loginTF, gbc);
gbc.gridwidth = 1;
panel.add(password, gbc);
gbc.gridwidth = GridBagConstraints.REMAINDER;
panel.add(passwordTF, gbc);
JButton loginButton = new JButton("Login");
loginButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
credentialsProvided = true;
proceed();
}
});
frame.add(panel);
frame.add(loginButton, BorderLayout.SOUTH);
frame.pack();
frame.setVisible(true);
SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() throws Exception {
Thread.sleep(10000);
return null;
}
@Override
protected void done() {
super.done();
dbWorkDone = true;
proceed();
}
};
worker.execute();
}
protected void proceed() {
if (credentialsProvided && dbWorkDone) {
System.err.println("Continuing ");
}
}
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException,
UnsupportedLookAndFeelException {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new TestThreadJoin().initUI();
}
});
}
}
回答2:
It can be done using SwingWorker. Add this ActionListener to your JButton. Also provide JLabel field for 'label' & JProgressBar field for 'progressbar'.
java.awt.event.ActionListener al = new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
SwingWorker swingWorker = new SwingWorker() {
@Override
protected Object doInBackground() throws Exception {
// SIMULATE DB WORK
for (int i = 0; i < 10; i++) {
synchronized (this) {
this.wait(300);
}
publish(10 * i);
}
publish(100);
return 0;
}
@Override
protected void done() {
label.setText("Work complete");
button.setEnable(true);
}
@Override
protected void process(List chunks) {
for (Object object : chunks) {
Integer progress = (Integer) object;
progressBar.setValue(progress);
}
}
};
button.setEnable(false);
swingWorker.execute();
}
}
Since DB work may not be quantifiable (measurable), do the following changes:
- insert 'progressbar.setIndeterminate(true)' above the 'swingWorker.execute()' line,
- insert 'progressbar.setIndeterminate(false)' above 'label.setText()' line,
- remove 'publish(int)' calls.
回答3:
I really think you should give the SwingWorker a try, by doing something like:
- when the user clicks login, start the swing worker (new Swingworker<...,...>().execute();)
- in the doInBackground(), call pusblih() first to call process() to disable the button
- then wait for the background thread in doInBackground()
- in the done() update the UI
this way you will not freeze the EDT...
Another option would be to run you own "mini-swingworker":
loginButton.setEnabled(false); // assuming this runs in the EDT
new Thread(new Runnable() {
public void run() {
myOtherBgThread.join(); // here we wait for the BG thread
SwingUtilities.invokeLater(new Runnable() {
// go back into the EDT
// update the UI here...
}
}
}.start();
hope it helps, regards
回答4:
One of the simplest solutions would be to disable Login button before the initial DB communication finishes. But that would require some additional progress bar or message that the initialization takes place not to confuse the users. In this case you could use
SwingWorker
which enables the Login button upon finish.Another solution is that the when the user presses the Login button, the handling should be again delegated to (another) background thread which waits for the initial DB communication thread to join. This way EDT (and GUI) will not be blocked. Actually connecting to DB is also a lengthy operation so it should be done in a background thread anyway, even when disabling the Login button as described in 1.
Yet another solution is to create a thread pool of fixed size 1 (eg.
java.util.concurrent.Executors.newSingleThreadExecutor()
) and pass all background tasks there. With single thread executor tasks will be performed in sequential order. The first task would be the initial DB communication, another one the login task.
回答5:
Use an ExecutorService. Call executorService.submit(runnable)
where 'runnable' performs the DB work. The submit
method will return a Future
. Then after login you can call future.get()
which will wait for completion (or return immediately if the runnable had already completed).
来源:https://stackoverflow.com/questions/14276095/java-updating-ui-while-waiting-for-another-thread-to-finish