Java:在等待另一个线程完成时更新UI

Java:在等待另一个线程完成时更新UI,java,swing,event-dispatch-thread,Java,Swing,Event Dispatch Thread,我有一些Java Swing登录面板。当用户单击登录按钮时,会发生广泛的数据库通信。我有一个想法,把db通信放到另一个线程中,db通信用于填充一些散列映射和单例。在将该通信放入单独的线程后,当用户键入其uname和密码时,数据库将发生重载。当用户单击登录按钮时,代码将等待重载线程加入,然后继续 问题是,当我的代码等待db线程加入时,似乎无法进行UI更新。我不认为我可以使用SwingWorker进行db通信,因为用户可以随时单击登录按钮,即使在软件完成之前,我也没有办法加入SwingWorker,

我有一些Java Swing登录面板。当用户单击登录按钮时,会发生广泛的数据库通信。我有一个想法,把db通信放到另一个线程中,db通信用于填充一些散列映射和单例。在将该通信放入单独的线程后,当用户键入其uname和密码时,数据库将发生重载。当用户单击登录按钮时,代码将等待重载线程加入,然后继续

问题是,当我的代码等待db线程加入时,似乎无法进行UI更新。我不认为我可以使用SwingWorker进行db通信,因为用户可以随时单击登录按钮,即使在软件完成之前,我也没有办法加入SwingWorker,因为在实际登录之前必须进行db通信

在等待另一个线程加入时,是否有办法启用Swing ui更新?我错过什么了吗

最简单的解决方案之一是在初始DB通信完成之前禁用登录按钮。但这将需要一些额外的进度条或消息,以确保初始化不会混淆用户。在这种情况下,您可以使用SwingWorker,它在完成时启用登录按钮

另一个解决方案是,当用户按下登录按钮时,处理应该再次委托给另一个后台线程,该线程等待初始DB通信线程加入。这样EDT和GUI就不会被阻止。实际上,连接到DB也是一个漫长的操作,因此无论如何,它都应该在后台线程中完成,即使在禁用1中所述的登录按钮时也是如此

另一个解决方案是创建一个固定大小为1的线程池,例如java.util.concurrent.Executors.newSingleThreadExecutor,并将所有后台任务传递给该线程池。对于单线程,执行器任务将按顺序执行。第一个任务是初始数据库通信,另一个任务是登录任务


这可以使用SwingWorker完成。 将此ActionListener添加到JButton。还为'label'提供JLabel字段,为'progressbar'提供JProgressBar字段

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();
    }
}
由于数据库工作可能无法量化,请执行以下更改:

在“swingWorker.execute”行上方插入“progressbar.setIndeterminatetrue”, 在“label.setText”行上方插入“progressbar.setIndeterminatefalse”, 删除“publishint”调用。
我真的认为你应该尝试一下SwingWorker,比如:

当用户单击登录时,启动swing worker new Swingworker.execute; 在doInBackground中,首先调用pusblih以调用process以禁用按钮 然后在doInBackground中等待后台线程 在“完成”对话框中,更新UI 这样你就不会冻结EDT

另一种选择是运行您自己的迷你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();

希望有帮助,只需放置两个标志,在DB通信和用户单击操作结束时,调用相同的方法并验证两个标志的状态。如果要阻止用户输入,可能会在模式对话框中显示进度条:

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();
            }
        });
    }
}

使用执行器服务。调用executorService.submitrunnable,其中“runnable”执行数据库工作。submit方法将返回Future。登录后,您可以调用future.get,它将等待完成,如果runnable已经完成,则会立即返回。

1。您的意思是要在输入凭据并单击“登录”后更新UI吗?比如更新进度条之类的?或者您只是希望用户能够输入凭据并单击“登录”,然后在数据库任务完成之前不执行任何操作?2。您当前使用的java并发机制是什么?普通线程、线程池、执行器等等?我用的是普通线程@赫尔曼:我想开始加载预登录数据,然后检查uname和pass,但如果用户快速输入凭据并单击login,我想完成预加载,然后继续检查uname和pass。要更快地获得更好的帮助,请发布一个。问题是登录过程的另一半是在另一个类中完成的。从swing worker,我真的不知道用户是否已经单击了登录按钮,从另一个类,我真的不知道swing worker是否已经完成了工作阶段1登录,或者说停止阶段2登录,直到swing worker完成…如果将ActionListener附加到登录按钮,当按下按钮,然后执行swingWorker时,您就知道了。在swingWorker.doInBackground中,执行您的登录调用,例如Controller.loginname、pwd。阅读MVC模式了解更多信息。问题是登录过程的另一半是在另一个类中完成的。从swing worker,我真的不知道用户是否已经单击了登录按钮,或者在密码字段中按下了Enter键!,从另一个类中,我真的不知道swing worker是否完成了工作阶段1登录,或者在swi之前停止阶段2登录
ng worker已完成…我已使用ScheduledThreadPoolExecutor,提交了该db线程,但再次调用Future.get会阻止UI,直到Executor完成…在调用该命令时,登录按钮已被单击,那么为什么您需要能够在等待时更新UI?你不能先更新UI,然后调用get吗?如果是autologin,那么第2阶段会立即在第1阶段之后。若手动登录阶段1在用户键入时完成,则阶段2在单击登录按钮后开始,可能在阶段1 db线程完成之后或之前。第2阶段是共享的-手动和自动登录都使用相同的方法。在自动登录的情况下,必须显示不同的UI,并立即调用阶段1和阶段2。因为stage1.join可以完成,但是因为join,所有事情都完成后,loding UI会闪烁。@guest86在EDT上调用get是个坏主意。它将冻结用户界面。没有人加入、等待、睡觉等。。。我会解决这个问题。我会接受这个解决方案。它是完整的,看起来对我来说很有用。即使没有,它的一些衍生物也能起作用。