Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prevent JavaFX thread from dying with JFXPanel Swing interop?

I'm embedding several JFXPanels into a Swing app and the JavaFX thread dies when the JFXPanels are no longer visible. This is problematic because creating another JFXPanel after the JavaFX thread dies will not start another JavaFX thread and thus the JFXPanel will be blank.

From what I can tell the JFXPanel ctor starts the JavaFX thread by calling:

PlatformImpl.startup(new Runnable() {
   @Override public void run() {
      // No need to do anything here
   }
});

Later on once the JFXPanel has a parent component its addNotify method is called which calls registerFinishListener which registers a PlatformImpl.FinishListener() with PlatformImpl. The act of registering the FinishListener prevents the JavaFX thread from dying when PlatformImpl.checkIdle() is called.

When a JFXPanel is no longer visible its removeNotify method is call which calls deregisterFinishListener():

private synchronized void deregisterFinishListener() {
    if (instanceCount.decrementAndGet() > 0) {
        // Other JFXPanels still alive
        return;
    }
    PlatformImpl.removeListener(finishListener);
    finishListener = null;
}

When instanceCount is zero the FinishListener is removed which causes PlatformImpl to call PlatformImpl.tkExit in the following code which causes the JavaFX thread to die:

private static void notifyFinishListeners(boolean exitCalled) {
    // Notify listeners if any are registered, else exit directly
    if (listenersRegistered.get()) {
        for (FinishListener l : finishListeners) {
            if (exitCalled) {
                l.exitCalled();
            } else {
                l.idle(implicitExit);
            }
        }
    } else if (implicitExit || platformExit.get()) {
        tkExit();
    }
}

The only way I've found to fix this issue is to call Platform.setImplicitExit(false) at the begining of the Swing app so that the JavaFX thread never dies automatically. This fix requires a call the Platform.exit() when the application exits otherwise the JavaFX thread will prevent the process from stopping.

This seems like a bug in JavaFX-Swing interop or at the very least the interop documentation should be modified to address this by discussing Platform.setImplicitExit(false).

Another solution would be to allow creation of a new JavaFX thread when another JFXPanel is created but that is blocked by PlatformImpl.startup(Runnable):

if (initialized.getAndSet(true)) {
   // If we've already initialized, just put the runnable on the queue.
   runLater(r);
   return;
}

Am I missing something?

like image 464
jenglert Avatar asked Aug 07 '14 22:08

jenglert


1 Answers

This is a really old "bug" that was somewhat fixed with the introduction of Platform.setImplicitExit(false). You can read the developers comments in the open issue JDK-8090517. As you will see it has a low priority and probably will never get fixed (at least not soon).

Another solution you might want to try instead of using Platform.setImplicitExit(false) is to extend the Application class in your current Main class and use the primary Stage to display the application's main window. As long as the primary Stage remains open the FX Thread will be alive (and dispose correctly when you close your app).

If you aren't looking to use an FX Stage as your main window (since it would require to use a SwingNode for what you have now or migrate your UI to JavaFX) you can always fake one like this:

@Override
public void start(Stage primaryStage) throws Exception {
    YourAppMainWindow mainWindow = new YourAppMainWindow();
    // Load your main window Swing Stuff (remember to use 
    // SwingUtilities.invokeLater() to run inside the Event Dispatch Thread
    mainWindow.initSwingUI();

    // Now that the Swing stuff is loaded open a "hidden" primary stage
    // that will keep the FX Thread alive
    primaryStage.setWidth(0);
    primaryStage.setHeight(0);
    primaryStage.setX(Double.MAX_VALUE);
    primaryStage.setY(Double.MAX_VALUE);
    primaryStage.initStyle(StageStyle.UTILITY);
    primaryStage.show();
}

Keep in mind that faking a primary stage (or migrating your main window to FX) will end in more code than simply using Platform.setImplicitExit(false) and Platform.exit() accordingly.

Anyway, hope this helps!

like image 169
JavierJ Avatar answered Oct 31 '22 23:10

JavierJ