Java Swing应用程序意外终止。

4
我是一位有用的助手,可以为您翻译文本。
我正在尝试使用Java编写一个Swing应用程序,该应用程序还运行Google AppEngine Dev-Server(请参见Developing a Java Application that uses an AppEngine database),但在Swing事件循环中遇到了奇怪的问题。
我有以下两个类:
1. 一个调试窗口,最终将接收日志消息等。
2. 一个主应用程序,它启动并运行Dev-Server。
public class DebugWindow {

    private static JFrame    debugWindow  = null;
    private static JTextArea debugContent = null;

    public static void show() {
        debugWindow = new JFrame("Debug");
        debugWindow.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        debugContent = new JTextArea("Debug messages go here!");
        debugWindow.add(debugContent, BorderLayout.CENTER);
        debugWindow.pack();
        debugWindow.setVisible(true);
    }
}

一个帮助类,用于加载Google AppEngine Dev-Server:
// other imports
import com.google.appengine.tools.development.DevAppServerMain;

public class DevServer {
    public static void launch(final String[] args, boolean waitFor) {
        Logger logger = Logger.getLogger("");
        logger.info("Launching AppEngine server...");
        Thread server = new Thread() {
            @Override
            public void run() {
                try {
                    DevAppServerMain.main(args);  // run DevAppServer
                } catch (Exception e) { e.printStackTrace(); }
            }
        };
        server.setDaemon(true);  // shut down server when rest of app completes
        server.start();          // run server in separate thread
        if (!waitFor) return;    // done if we don't want to wait for server
        URLConnection cxn;
        try {
            cxn = new URL("http://localhost:8888").openConnection();
        } catch (IOException e) { return; }  // should never happen
        boolean running = false;
        while (!running) {
            try {
                cxn.connect();  // try to connect to server
                running = true;
            } catch (Exception e) {}
        }
        logger.info("Server running.");
    }
}

我的 main(...) 方法看起来像这样:

public static void main(final String[] args) throws Exception {
    DevServer.launch(args, true);  // launch and wait for AppEngine dev server
    SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
            DebugWindow.show();  // create and show debug window
        }
    });
}

我遇到了一些关于Swing事件循环的奇怪行为:
  1. 首先,Swing应该是这样工作的:如果我在main(...)中注释掉DevServer.launch(...)这一行,应用程序会启动,显示调试窗口,继续运行,当我关闭调试窗口时,它会关闭。
  2. 如果我重新添加DevServer.launch(...),它会如预期地启动服务器,然后立即退出(可能还会短暂显示调试窗口,但太快看不到)。
  3. 如果我将DevServer.launch(...)移动到SwingUtilities.invokeLater(...)之后,它会显示调试窗口,然后启动服务器,当服务器启动后,它会立即退出。
  4. 现在变得非常奇怪:如果我将该行更改为DevServer.launch(args, false),即我不等待服务器实际启动,而只是让我的main(...)方法立即完成,调试窗口会显示,服务器会正确加载,应用程序会继续运行,但如果我关闭调试窗口,它不会退出?!
  5. 然后,如果我还将JFrame.DISPOSE_ON_CLOSE更改为JFrame.EXIT_ON_CLOSE,调试窗口会显示,服务器会正确加载,应用程序会继续运行,并且如果我关闭调试窗口,它会正确退出。
有什么想法关于这里的Swing事件循环发生了什么?我被卡住了...是否有一些事情会导致Swing事件循环提前终止(情况2和3)?多线程应用程序是否会阻止Swing检测到最后一个已释放的窗口(情况4)?
供参考,这是Google AppEngine Dev Server的源代码

当你捕获到一个“绝不应该发生”的异常,比如上面的 IOException ,将其重新抛出为 RuntimeException 总是一个好主意,以证明你的观点。浅层抛出从来不是一个好主意。 - c.s.
@c.s. 同意。其实我为了这个原因自己定义了一个 ShouldNeverHappenException 对象,我总是使用它。我只是从代码示例中删除了它,因为我不想为了完整性再发布另一个类... - Markus A.
1个回答

1
项目#4和#5实际上是预期的行为。Java / Swing应用程序在最后一个Swing窗口被销毁时不会停止,而是在最后一个线程停止执行时停止。这两个条件对于单线程应用程序是等效的,但对于多线程应用程序则不是。
至于#1,#2和#3:通过查看AppEngine Dev Server代码,我注意到其中有相当数量的System.exit(int)调用。其中一个可能是罪魁祸首。如果您展示的代码是所有相关内容,则由于#4,触发System.exit的有害连接可能在if(!waitFor)return; 之后建立。

你可能是正确的,#4 是由 Dev Server 中某个未关闭的线程引起的,但如果 #2 和 #3 是由 Dev Server 中的 System.exit(...) 之一引起的,为什么 #4 和 #5 不会立即退出呢? - Markus A.
#4和#5似乎并没有真正连接到开发服务器。至少在您发布的代码中没有。我猜测开发服务器启动(任何在if(!waitFor)return;以上的内容)是正常的,但是开发服务器的连接处理(仅在waitFortrue时触发)会导致它发出System.exit调用。 - Daan
奇怪的是,如果我在DevServer.launch(args, true)之后放置一个System.out.println(...),它确实会被执行,因此仅连接的操作并不会终止程序。此外,在情况#2或#3的main(...)方法末尾放置一个Thread.sleep(10000),服务器将运行10秒并处理请求,但是一旦main(...)方法实际终止,应用程序就会退出。因此,应用程序关闭实际上是由于main(...)方法完成触发的,就好像Swing事件循环不再运行一样... - Markus A.
情节越来越复杂:https://dev59.com/wXTYa4cB1Zd3GeqPxrCD(rahulserver的答案)。看起来Dev Server线程应该自动标记为守护进程。浏览Dev Server代码,也只有setDaemon(true)调用,即没有任何线程被标记回非守护进程状态。这意味着在情况#4中不应该有任何挂起的线程使程序继续运行...奇怪... - Markus A.
确实很奇怪。我很想看到从EDT输出的Thread.isDaemon()结果。虽然可能性不大,但如果Dev服务器线程的守护进程状态以某种方式传递到EDT,那么这可能会解释#1、#2和#3。 - Daan
不幸的是,这也无法解释。我检查了 main(...) 线程的守护进程状态,但它没有改变(始终为 false)。在场景2中,在 invokeLater(...) 调用的 run(...) 方法内部的 EDT 也没有声称自己是一个守护进程。 - Markus A.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接