Search code examples
javafxjavafx-8undertowundertowjaxrsserver

Update JavaFX control from thread started by a library


I have a JavaFX 8 application. It uses kind of observer pattern to react to events:

public class EventBus {

    public static final EventBus INSTANCE = new EventBus();

    private final Set<EventListener> listeners;

    private EventBus() {
        listeners = new HashSet<>();
    }

    public void register(EventListener listener) {
        listeners.add(listener);
    }

    public void fire(Event event) {
        listeners.forEach(listener -> listener.eventFired(event));
    }
}

It worked pretty well so far for all my usecases:

  • Interaction with buttons
  • Events from external controllers, which I observe with Platform.runLater() initiated thread
  • etc.

Now I want to start embedded Undertow HTTP server and deploy a JAX-RS endpoint inside. Whenever a request comes in there, I want to fire an event from within that endpoint using the above event bus. So, this is how I start Undertow and deploy my JAX-RS app:

UndertowJaxrsServer server = new UndertowJaxrsServer().start();
server.deploy(new MyEndpoint(eventBus));

The thing is that new UndertowJaxrsServer().start() is asynchronous call and it starts a thread of itself. And then when MyEndpoint handles a request and tries to fire an event, it happens from that thread, which was started by Undertow.

So in the event listener, if I try to do any updates on the UI, I get:

java.lang.IllegalStateException: Not on FX application thread; currentThread = XNIO-1 task-1
    at com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:236)
    at com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:423)
    at javafx.scene.Parent$2.onProposedChange(Parent.java:367)
    at com.sun.javafx.collections.VetoableListDecorator.clear(VetoableListDecorator.java:294)
    at ui.DialogManager.showDialog(DialogManager.java:109)

Finally, my question is is there a way to explicitly tell JavaFX to run a piece of code in the UI thread, even if the call stack comes from another one (to which I do not have control and on which I can't do Platform.runLater()).

Thanks!


Solution

  • If MyEndpoint is your class then surround invocation of fire(Event event) method with Platform.runLater() or change EventBus class:

    public class EventBus {
    
        public static final EventBus INSTANCE = new EventBus();
    
        private final Set<EventListener> listeners;
    
        private EventBus() {
            listeners = new HashSet<>();
        }
    
        public void register(EventListener listener) {
            listeners.add(listener);
        }
    
        public void fire(Event event) {
            Platform.runLater(() -> {
                listeners.forEach(listener -> listener.eventFired(event));    
            });
        }
    }