Java: Updating UI while waiting for another thread to finish

不打扰是莪最后的温柔 提交于 2021-02-11 15:01:35

问题


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:


  1. 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.

  2. 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.

  3. 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!