e(fx)clipse runtime library – Working with the (JavaFX) UI-Thread


When developing UI-Application a very frequent task is

  • to synchronizes yourself back from an worker-thread to the UI-Thread
  • schedule task to run in the future
  • Block the program flow and wait for a condition being met (in SWT called spinning the event loop)

JavaFX 8 has a public API for most of those building blocks (only exception is Event-Loop-Spinning who was an internal API in Java8 and is public API in 9) but using higher level API reduces the boilerplate code you need to implement.

Before we start let’s see how you can access to it in your maven-driven projects (I’m not gradle safey enough to show how it works there) you need to

  • add the efxclipse maven repository (at the time of this writing you need to nightly repository because you need at least 3.0.0)
  • add the following dependency
    <dependency>
      <groupId>at.bestsolution.efxclipse.rt</groupId>
      <artifactId>org.eclipse.fx.ui.controls</artifactId>
      <version>3.0.0-SNAPSHOT</version>
    </dependency>
    

Let’s take a look at some of those APIs and how they can help you write better code:

Writing back from worker thread

Let’s suppose we have a service who returns a search result as a CompletableFuture and we want to push the result – once available – to JavaFX ObjectProperty:

import org.eclipse.fx.core.ServiceUtils;

// Lookup the domain service in the service registry
PersonSearchService service = 
  ServiceUtils.getService(PersonSearchService.class);

ObjectProperty<Person> person = ...;

CompletableFuture<Person> result = 
  service.findByName("Tom","Schindl");

Let’s for a second suppose we can interact with our UI-Toolkit on ANY-Thread we could simply write:

result.thenAccept( person::set );

Unfortunately none of the UI-Toolkits I know would support this because they require you to sync on a special thread called the UI-Thread. JavaFX is not different in this aspect.

Plain JavaFX APIs:

result.thenAccept( p -> {
   Platform.runLater( () ->
     person.set(p);
   );
} );

Using e(fx)clipse’ ThreadSynchronize#asyncExec(T,Consumer<T>):

import org.eclipse.fx.core.ThreadSynchronize;

// ...

// Lookup the thready service in the service registry
ThreadSynchronize threadSync = 
  ServiceUtils.getService(ThreadSynchronize.class).get();

result.thenAccept( p -> {
   threadSync.asyncExec( p, person::set );
});

We got rid of the inner lambda and replaced it with a method reference, so the code got more readable but

Using e(fx)clipse’ ThreadSynchronize#wrap(Consumer<T>):

// ...
result.thenAccept( threadSync.wrap(person::set) );

we can get rid of all outer lambda as well and are back to fairly the same code as if we’d not had to worry about thread-synchronization at all.

Reading from a worker thread

Let’s suppose you have a Timer running who wants to read a JavaFX-Property from a TextField in 1 second from now you’d:

  • You’d better rewrite it to use a Timeline so that no thread sync is needed
  • You’d write the following magic lines of code:
TextField textField = ...

Timer t = new Timer();
t.schedule( new TimerTask() {
  @Override
  public void run() {
    CountDownLatch ll = new CountDownLatch(1);
    AtomicReference<String> data = new AtomicReference<>();
    Platform.runLater( () -> {
      data.set( textField.getText() );
      ll.countDown();
    });
    ll.await();
    String d = data.get();
    // further process the data 
  }
}, 1000 );

Let’s for a moment forget that this code fairly dangerous because it might create a dead lock situation (hence JavaFX only provides Platform.runLater(Runnable)) this is a huge amount of code to write! Let’s see what APIs e(fx)clipse has to improve the situation.

Using ThreadSynchronize#syncExec(Runnable):

ThreadSynchronize threadSync = 
  ServiceUtils.getService(ThreadSynchronize.class).get();

public void run() {
  AtomicReference<String> data = new AtomicReference<>();
  threadSync.syncExec( () -> {
    data.set( textField.getText() );
  } );
  String d = data.get();
  // further process the data 
}

Removes the need for the CountDownLatch

Using ThreadSynchronize#syncExec(Callable<V>, V):

public void run() {
  String d = threadSync.syncExec( textField::getText, "" );
}

Removes the need for the AtomicReference

Using ThreadSynchronize#scheduleExecution(long, Runnable):

ExecutorService s = ...;
threadSync.scheduleExecution( 1000, () -> {
   String data = textField.getText();
   s.submit( () -> ... );
} );

Removes the thread synchronization problems arising from Platform.runLater() call

Using ThreadSynchronize#scheduleExecution(long, Callable<T>) : CompletableFuture<T> :

threadSync.scheduleExecution( 1000, textField::getText )
  .thenAcceptAsync( d -> ... );

Removes the lambda and gets you to the wonderful CompletableFuture-API.

Block program flow

Generally speaking halting the program flow is a discouraged software pattern and you’d better work with Future and callbacks like Consumer but there might be (existing) API you have to support who requires you to halt the program flow and continue after a certain condition has been met.

To support such a usecase e(fx)clipse has org.eclipse.fx.ui.controls.Util#waitUntil( BlockCondition blockCondition ) you can use like this


Pane p = ...;

// Ask for name in an overlay
String askForName() {
  BlockCondition<String> condition = new BlockCondition<>();

  TextField t = new TextField();
  Button b = new Button("Proceed");
  b.setOnAction( e -> { condition.release(t.getText()); } );
  HBox box = new HBox(
    new Label("Name:"),
    t,
    b);
  box.setManaged(false);
  box.autosize();
  box.relocate( 
    p.getWidth() / 2 - box.getWidth() / 2, 
    p.getHeight() / 2 - box.getHeight() / 2 );
  p.getChildren().add( box );
  return Util.waitUntil( condition );
}
This entry was posted in e(fx)clipse. Bookmark the permalink.

1 Response to e(fx)clipse runtime library – Working with the (JavaFX) UI-Thread

  1. Pingback: JavaFX links of the week, March 27 | JavaFX News, Demos and Insight // FX Experience

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.