0

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!

ivko
  • 58
  • 4

1 Answers1

3

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));    
        });
    }
}
MBec
  • 2,172
  • 1
  • 12
  • 15
  • 1
    As an optimization you could also run a check within fire, [Platform.isFXApplicationThread()](https://docs.oracle.com/javase/8/javafx/api/javafx/application/Platform.html#isFxApplicationThread--), and just runLater if not on the FX thread, otherwise execute immediately. – jewelsea Oct 12 '17 at 19:12
  • Thanks a lot! The first proposal (surrounding the invocation of `fire(Event event)`) worked perfect! – ivko Oct 12 '17 at 21:52