1

I have a working JavaFX application. It has three main parts:

  1. A list of signals visible globally to the entire application. Each signal has a String value property that is observable. This list is instantiated before the JavaFX scene is constructed, the signal list constructor is run in the Application FX thread.

  2. A JavaFX table implemented as an Observable Array List so that as signal values change they are automatically updated on the GUI.

  3. A simulation engine that runs a loop that changes signal values. This loop is run in a worker thread.

I am fully aware that GUI elements like selection lists, text in boxes, etc. can only be updated in the Application FX thread. I use Platform.runLater(someRunnableThing) to do that. However, what blindsided me was that even changing a signal value, which changes the value of the observable String property, must be done in the FX thread or not-in-Application-FX-thread exceptions will be thrown.

Curiously the application still works fine despite these exceptions, because eventually (instantaneously to a human observer) the changed value is picked up and displayed. I only noticed this when doing final checks of run-time behavior before release.

It is a very common thing for a worker thread to be changing variables in the background while a GUI is displaying information based on the changing variables. Platform.runLater() is expensive and somewhat non-deterministic. Since the worker thread is not touching the GUI and the application FX thread can choose to grab updates whenever it wants it seems draconian to me for Java to force this behavior.

Have I missed something about modifying observed properties? Any thoughts and ideas appreciated.

BillTrib
  • 29
  • 2

1 Answers1

2

There are no rules about updating JavaFX properties from background threads. The only rule is that you cannot update nodes that are part of a scene graph from a background thread, and there are no plans (and likely never will be) to relax that rule.

You didn't post any code, so we can only make educated guesses as to what the actual problem is. What is likely happening is that you have a listener or a binding on one of the properties (or observable collections) that is being changed from your background thread, where the listener/binding is updating the UI. Listeners with observables (including listeners created and registered by bindings) are, of course, invoked on the same thread on which the observable is changed.

So if you have something like

someApplicationProperty.addListener((obs, oldValue, newValue) -> {
    someUIElement.setSomeValue(...);
});

or perhaps

someUIElement.someProperty().bind(someApplicationProperty);

just replace it with

someApplicationProperty.addListener((obs, oldValue, newValue) -> {
    Platform.runLater(() -> someUIElement.setSomeValue(...));
});

In other words, you can continue to update your application properties from the background thread, as long as your listener updates the UI from the FX Application Thread.

In the case where the listener is registered by the UI component itself, you must ensure that the observable with which the listener is registered is changed on the UI thread. This is the case in the example you allude to, for example updating the backing list for a ListView or TableView. You can do this either by directly invoking Platform.runLater(), or by placing a layer in between the model and the UI. For the latter approach, see Correct Way to update Observable List from background thread

Also maybe see http://www.oracle.com/technetwork/articles/java/javafxinteg-2062777.html

James_D
  • 201,275
  • 16
  • 291
  • 322
  • Thank you for this explanation, Jim. I did not realize that the listener was invoked on the thread that made the change. This is where my code was firing the exception. Both of the articles you referenced were helpful. This one also has an example of the layer approach: back end store has no Observables, updates of observables are controlled and only happen on the Application FX thread: https://stackoverflow.com/questions/20332967/concurrency-issues-in-javafx-gui-updater-utility-class – BillTrib Oct 17 '17 at 11:47