如何停止“JavaFX应用程序线程”

17
我有一个简单的控制台应用程序,有时需要执行图形操作,为此我使用JavaFx框架(有一些我需要的函数,例如文本的css样式)。 我只需在隐藏场景中生成一些形状和文本,然后将它们保存到文件中即可。
我知道要使用JavaFx,必须将图形操作传递给JavaFx线程,但当所有事情都完成并且我必须关闭应用程序(几个小时后),这个JavaFx线程仍然保持打开状态...我真的不想强制退出System.exit(),因为如果某些东西被阻止,我可能想要知道/等待(而且我不想将所有内容都作为JavaFx应用程序执行(因为JavaFx组件少于我的主要应用程序的1%))
代码非常简单,通过搜索我发现只能使用
Platform.exit();

这似乎不起作用,我也尝试过调整平台参数,比如

Platform.setImplicitExit(false);

这是我的测试应用程序,您可以运行:

import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.geometry.Pos;
import javafx.scene.layout.VBox;

public class SOTestFX {
    public static void main(String[] args) {
        SOTestFX t = new SOTestFX();
        t.runFxThread();
    }

    public  void runFxThread(){
        //Application.launch(args);
        final JFXPanel jfxPanel = new JFXPanel(); 
        Platform.runLater(new Runnable() {
            @Override
            public void run() {

                System.err.println("CREATING IMAGE");
                simpleFXoperations();
                System.err.println("NOW CALL EXIT");
                System.err.println("JAVA FX THREAD SHOULD BE EXITED NOW");
                Platform.exit();
            }
        });

        try {
            Thread.sleep(3000); // just wait a bit if something should happen, let it happen..
        } catch (InterruptedException e) {
            e.printStackTrace();  
        }
        //jfxPanel.removeNotify(); // return ->  java.lang.NullPointerException
        //Platform.exit(); // -> does nothing

        System.err.println("i will never die!");
    }
    public void simpleFXoperations(){

        VBox vbox1 = new VBox();
        vbox1.setAlignment(Pos.BOTTOM_CENTER);
        vbox1.setStyle("-fx-border-style: solid;"
                + "-fx-border-width: 1;"
                + "-fx-border-color: white");
        System.err.println("simpleFXoperations() _DONE");
    }
}

这是一个从未关闭的线程:

"Attach Listener" - 线程 t@17 java.lang.Thread.State: RUNNABLE

已锁定的可拥有同步器: - 无

"JavaFX 应用程序线程" - 线程 t@13 java.lang.Thread.State: RUNNABLE at com.sun.glass.ui.gtk.GtkApplication._runLoop(Native Method) at com.sun.glass.ui.gtk.GtkApplication$3$1.run(GtkApplication.java:82) at java.lang.Thread.run(Thread.java:722)

已锁定的可拥有同步器: - 无

更新:我正在使用 Linux Fedora 16 64位上最新的 Oracle JDK 7u17 64位。


我对此没有任何答案,也不知道为什么 Platform.exit 在你的测试用例中无法工作。请尝试在 https://javafx-jira.kenai.com 提交一个问题。 - jewelsea
谢谢,但是这对你有效吗?你有类似的测试用例,在它可以运行的情况下吗?在我的所有测试中,似乎Platform.exit()根本不起作用... :( - Francesco
4个回答

24

解决方案:

我通过在调用Platform.exit()之前立即调用com.sun.javafx.application.PlatformImpl.tkExit()来解决了这个问题。虽然我不是很理解JavaFX的源代码,但它似乎可以解决问题; 可能因人而异。

更新:在Java 8中执行此操作会产生警告,您可以使用@SuppressWarnings("restriction")关闭警告。这不应该成为问题。

说明:

我通过查看源代码找到了解决方法;JFXPanel 中有这段小代码片段(这是从 JavaFX 2.2.25中提取的)

finishListener = new PlatformImpl.FinishListener() {
  public void idle(boolean paramAnonymousBoolean) {
    if (!JFXPanel.firstPanelShown) {
      return;
    }
    PlatformImpl.removeListener(JFXPanel.finishListener);
    JFXPanel.access$102(null);
    if (paramAnonymousBoolean)
      Platform.exit();
  }

  public void exitCalled()
  {
  }
问题是,如果你的应用程序只使用了一点点JavaFX,那么idle(boolean)方法永远不会执行任何操作(因为firstPanelShown == false),这将防止侦听器被移除,从而阻止JavaFX Toolkit关闭... 这意味着你必须手动关闭它。

这很有道理,但在应用程序部署时并没有帮助。我已经看到过JavaFX应用程序终止,但它仍然通过NetBean任务状态在后台运行活跃。当它正在运行(或活动)时,您无法删除JAR文件或重新启动应用程序而不重新启动计算机! - will
依赖于com.sun方法不是我想要做的事情。特别是考虑到OpenJDK和/或Java 9。 - Simon Forsberg
我已经阅读了你的这篇meta回答(http://meta.stackoverflow.com/a/253066/3375713),并认为你的辛勤工作不应该没有得到赞赏。所以,我正在尽我所能:+1 :-) - Sнаđошƒаӽ
抛出 java.lang.IllegalStateException: Toolkit has exited - shinzou
@kuhaku 这个答案已经过时了,这种情况可能会发生在您进行内部、未记录的 API 调用时 :-/ - durron597

2

您的主函数不属于JavaFx应用程序对象,我认为您的程序从未进入应用程序线程循环。

看起来您应该这样做:

public class SOTestFX extends Application {
    public static void main(String[] args) {
        launch(args);
    }
    @Override
    public void start(Stage stage) throws Exception {
        // Do stuff here to show your stage or whatever you want;
        // This will be called on the JavaFX thread
    }
}

1
但我不想因为一个小于800行的类需要使用JavaFX来处理一些图像而包含整个应用程序(超过40k行代码)。该应用程序甚至不是图形界面应用程序,而是控制台应用程序。无论如何,谢谢。 - Francesco
看起来确实是问题,看一下在应用程序的启动方法中调用的LauncherImpl的源代码:https://bitbucket.org/openjfxmirrors/openjfx-8-graphics-rt/src/1444a737ab40db93f4fee4f13d7122858ef5fad4/javafx-ui-common/src/com/sun/javafx/application/LauncherImpl.java?at=default - zenbeni

2

我理解JavaFX线程的目的是为了透明地利用各种硬件流水线,所以这是一个非常微妙的情况。我建议将JavaFX请求放置在单独的、被引用的项目中,而保留其他所有内容,包括主方法,都在另一个项目中。这对我来说一直都有效。

基本上,业务逻辑和模型放在一个项目中,视图和控制(通常是基于JavaFX的)放在另一个项目中。这样可以独立终止JavaFX线程,希望这对你正在尝试做的事情有帮助。


是的,我同意分离的观点。但是当您希望控制台应用程序的用户选择文件时,该怎么办呢?比如说,您肯定会允许他们使用FileChooser,而不需要Stage吧?另外,我目前发现Platform.exit()可以很好地关闭JavaFX线程。 - mike rodent

1

我尝试了很多方法,因为以上的答案都对我没用。 对我来说最好的方法就是使用关闭整个JVM。

System.exit(1);

由于我的整个应用程序只是一个JavaFX应用程序,因此在关闭舞台时可以关闭它。在我这种情况下,1只是一个随机的整数。


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