1

Java 不是我的母语,我一直在与这个问题作斗争一段时间。

基本上,我发现直接从 init() 调用方法 switchApplets() 和从 init() 产生的新线程中调用它之间的行为差​​异。

从新线程内部调用它的结果是新的小程序白屏——直到/除非用户调整大小或最小化他们的浏览器。如果在 init() 结束时调用,新 UI 会立即呈现,无需用户输入任何内容。但这不是一个选项,因为它不会等待线程完成其准备工作。

精简代码:

public class PreLoader extends Applet implements AppletStub {

static JProgressBar pBar = null;
static JLabel message;

public void switchApplets() {
    try {
        Class main_class = Class.forName("MainClass");
        Applet main_applet = (Applet)main_class.newInstance();
        removeAll();
        setSize(0,0);
        setLayout(new GridLayout(1,0));
        add(main_applet);
        main_applet.init();
        main_applet.start();
        main_applet.setStub(this);
    }
    catch (Exception e) {
    }
}

public void init() {

    pBar = new JProgressBar(0, 100);
    pBar.setValue(0);
    pBar.setStringPainted(true);

    message = new JLabel("Beginning work!");

    add(message);
    add(pBar);

    FlowLayout flow = new FlowLayout();

    setLayout(flow);

    Thread t = new Thread ( new Runnable () {
        public void run ()
        {
            longRunningFunction1();
            longRunningFunction2();
            message.setText("Work complete! Stand by..");
            switchApplets(); //does NOT work as intended from here
            return;
        }
    } );
    t.start();
    //switchApplets(); //works as intended if called HERE
}

public void longRunningFunction1() {
    //perform some tasks, advance progress bar
}

public void longRunningFunction2() {
    //perform some tasks, advance progress bar
}

public void start() {
    return;
}

public void appletResize(int width, int height) {
    return;
}

}

我尝试让 init() 等待线程完成,以便我可以从那里调用 switchApplets() ,但这只会阻止 EDT 并阻止 UI 更新。还尝试使用 SwingUtilities 的 invokeLater/invokeAndWait,但即使 switchApplets() 在 EDT 上运行,似乎必须直接从 init() 调用它(或至少在线程 init 上运行)才能获得所需的效果.

为什么从新线程中调用 switchApplets() 会导致 UI 行为略有不同(和不需要的)?

4

1 回答 1

0

从新线程内部调用它的结果是新的小程序白屏——直到/除非用户调整大小或最小化他们的浏览器。

这可能是由于尝试在错误的线程上执行 UI 代码而导致的死锁。

我尝试让 init() 等待线程完成,以便我可以从那里调用 switchApplets() ,但这只会阻止 EDT 并阻止 UI 更新。

你在正确的轨道上。您只需要从 EDT 调用 switchApplets(),并且只有在另一个线程上完成工作之后。

您确定在长时间运行的函数完成后尝试在衍生线程中使用 invokeLater() 或 invokeAndWait() 吗?自从我做小程序以来已经有很长一段时间了,但我不知道任何特定于小程序的原因为什么它不起作用,并且在任何其他情况下它都会起作用。IE,

public void run()
{
    longRunningFunction1();
    longRunningFunction2();
    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            message.setText("Work complete! Stand by..");
            switchApplets();
        }
    });
}

但是,最合适的方法是使用SwingWorker而不是手动创建的线程。SwingWorker(并不像它应该的那样出名)正是为在单独的线程上执行后台任务而设计的,同时仍然能够使用进度更新和结果来更新 GUI。例如,

new SwingWorker<Void,Void>() {
    @Override
    protected Void doInBackground() { // is called on a background thread
        longRunningFunction1();
        longRunningFunction2();
        return null;
    }

    @Override
    protected void done() { // is called on the Swing thread
        message.setText("Work complete! Stand by..");
        switchApplets();
    }
}.execute();

Void是因为 SwingWorker 也能够返回结果并发送中间进度更新,但是这个示例没有使用这些功能。

您表示您长期运行的函数也在更新进度条。这是另一件应该只在 Swing 线程上发生的事情。在实践中,您通常可以在没有它的情况下侥幸逃脱,但这很狡猾。您的进度更新可以使用 SwingUtilities.invoke 方法之一,或者 SwingWorker 的机制;要么应该工作。(SwingWorker 本身提供了两种不同的实现方式:调用addPropertyChangeListener(Swing 线程)和setProgress(后台线程),或者调用publish(后台线程)和覆盖process(Swing 线程)。)

另外,一个小建议:如果处理受检异常不方便(或无法有效地这样做),而不是捕获和忽略它,您至少应该捕获并重新将其作为未受检异常抛出:

catch (Exception e) {
    throw new RuntimeException(e);
}

这样,任何异常的堆栈跟踪和错误消息都不会丢失。

于 2013-10-28T04:47:39.637 回答