36

I have a JavaFX application, and a worker thread, implemented via javafx.concurrent.Task, that performs a long process, that is zipping and uploading a set of files.

I've connected the task progress to a progress bar via progressProperty.

In addition to this I want a detailed state about the item being processed to be reported into the UI. That is, the name of the file being processed along with its size and any error that may arise from the single file process.

Updating the UI with these information cannot be done from the worker thread, at the most I can add it to a synchronized collection.

But then I need some event to inform the UI that new data is available.

Does JavaFX have some specific support for this issue?

Update, better formulation

Instead of designing an ad hoc cross-thread mechanism as Platform.runLater, I'm trying to allow each property to be listened from other threads. Just like runningProperty and stateProperty provided by Task.

Lii
  • 11,553
  • 8
  • 64
  • 88
AgostinoX
  • 7,477
  • 20
  • 77
  • 137
  • Honestly, it sounds like you should just have an observable list and have a component (table/custom UI, whatever) listen to it. http://docs.oracle.com/javafx/2/collections/jfxpub-collections.htm In short, I think you're looking to make this a lot more complicated than it is. – Daniel B. Chapman Jul 29 '12 at 18:35
  • 1
    Yes but the fact that insertions happens in the worker thread and observation happens in the JavaFx Application thread. If no countermesures are taken, you get java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-3. At the end, i've encapsulated observable property modification into a Platform.runLater. – AgostinoX Jul 29 '12 at 18:49
  • I'll take a look back at this after I finish wrapping up my framework (I'm doing similar things, not the same, but similar enough). Have you tried running it on the beta 2.2? The way you describe doing it should work as the UI thread should "observe" those changes. Are you registering the listeners with the components? – Daniel B. Chapman Jul 30 '12 at 17:16

2 Answers2

41

I'm running into a similar issue, as far as I can tell you have to deal with the error handling yourself. My solution is to update the UI via a method call:

Something like:

  try
  {
    //blah...
  }
  catch (Exception e)
  {
    reportAndLogException(e);
  }
  ...
  public void reportAndLogException(final Throwable t)
  {
    Platform.runLater(new Runnable() {
      @Override public void run() {
        //Update UI here     
      }
    });
  }

Essentially I am just manually moving it back to the UI Thread for an update (as I would do in pretty much any other framework).

Daniel B. Chapman
  • 4,647
  • 32
  • 42
  • 1
    Ok, this is the first solution i thought. But i think that javafx is designed for a completely different approach. I mean, instead of designing an ad hoc cross-thread mechanism as Platform.runLater, i'm trying to allow each property to be listened from other threads. Just like runningProperty and stateProperty provided by Task. – AgostinoX Jul 29 '12 at 10:47
  • 2
    Right, but an exception is the exception to the case. Generally you want to report that immediately and this isn't adhoc, it is the standard FX method for rejoining the UI thread for an update. I agree that the rest follow a resting pattern, but by no means does that mean it must. Hopefully someone else will have a slick answer (edification is good!) – Daniel B. Chapman Jul 29 '12 at 14:04
  • Well, i should have spoken about "event" rather than exception. Exception is something that invalidates the whole process, while the process was required to report a failure and continue with the next item. So i need to report to the UI detailed information about the failure, but not stop the thread. This information goes beyond the basic progress-oriented properties provided by Task, but i want a similar (so neat!) method to communicate with the UI thread to be available for any piece of information! I'm going to edit the question to clarify this. – AgostinoX Jul 29 '12 at 14:16
  • @DanielChapman Is the error being caught in the Task.call()? But call() can throw an exception. – likejudo Dec 22 '12 at 17:20
  • @Anil I'm suggesting that you need to wrap the "dangerous" code and handle updates yourself. The problem presented above is that the UI needs to be updated ("Ooops and error occured!") after the exception. I'm suggesting there isn't (that I know of) clever way to handle that. As such my solution just handles the exception via logging and updating the UI. (I'm assuming this is unintentional). – Daniel B. Chapman Dec 22 '12 at 17:28
7

This answer uses the same concept as Daniel's answer.

Enclosed is a copy of the Partial Result sample from the Task javadoc (fixed for syntax errors currently embedded in the Java 8 javadoc and to add more specific Generic types). You can use a modification of that.

Place your exceptions in the partialResults collection. For your case, you don't need to return the list of exceptions from the Task, but can instead place them in some UI control which displays the exceptions (like a ListView with a CellFactory for exception display). Note that the partialResults collection did not need to be synchronized because it is always updated and accessed on the JavaFX UI thread (the update is happening via a Platform.runLater() call similar to Daniel's solution).

public class PartialResultsTask extends Task<ObservableList<Rectangle>> {
    private ReadOnlyObjectWrapper<ObservableList<Rectangle>> partialResults =
            new ReadOnlyObjectWrapper<>(
                    this, 
                    "partialResults",
                    FXCollections.observableArrayList(
                            new ArrayList<>()
                    )
            );

    public final ObservableList<Rectangle> getPartialResults() {
        return partialResults.get();
    }

    public final ReadOnlyObjectProperty<ObservableList<Rectangle>> partialResultsProperty() {
        return partialResults.getReadOnlyProperty();
    }

    @Override
    protected ObservableList<Rectangle> call() throws Exception {
        updateMessage("Creating Rectangles...");
        for (int i = 0; i < 100; i++) {
            if (isCancelled()) break;
            final Rectangle r = new Rectangle(10, 10);
            r.setX(10 * i);
            Platform.runLater(() -> partialResults.get().add(r));
            updateProgress(i, 100);
        }
        return partialResults.get();
    }
}

When updating it's observable properties Task first checks if the update is occurring on the FX Application thread. If it is, it does an immediate update. If it is not, then it wraps the update in a Platform.runLater() call. See the Task source code to understand how this is done.

Perhaps it would be possible to define a set of generic concurrent aware properties, but JavaFX does not provide such facilities at it's core. Indeed, it doesn't need to. With the exception of the javafx.concurrent package, JavaFX is a single threaded UI framework.

jewelsea
  • 150,031
  • 14
  • 366
  • 406
  • 1
    Ok, I was a little 'discouraged by the verbosity of the example :-) but this is very similar to what I want. The idea is that new javafx Observable properties model DOESN'T PROVIDE BY ITSELF A THREADSAFE notification mechanism (that is implemented specifically in Task properties). To get the two things (that is, a granularity at the property level AND thread safe notification mechanics) just use them together :-). – AgostinoX Jul 29 '12 at 15:06
  • Yes AgostinoX. I updated the answer to reference how Task source internally handles Concurrent property management. – jewelsea Jul 30 '12 at 09:40
  • This has to be outdated. partialResults.get().add() is no longer available. I used an approach similar to the one above. I made my class that does all of the work Runnable. Then I created an ObservableArrayList in both the class that does all of the work and the controller class. I then passed the ObservableArrayList from the controller to the Runnable when I created the object. Next, I assigned the Controller's ObservableArrayList to the Runnable's ObservableArrayList. Finally I used Platform.RunLater to add to the Runnable's ObservableArrayList. – SedJ601 Dec 14 '15 at 23:25
  • No, the answer was not outdated, it was just slightly incorrect. The answer was a copy and paste of original source from the Task javadoc which contained syntax errors. I edited the answer to fix the syntax errors and `partialResults.get().add()` now resolves correctly. I am unsure of your approach Sedrick, you could add your own answer which outlines the code for it. In general, I advise sticking with the pattern outlined in the Task javadoc. – jewelsea Dec 14 '15 at 23:42
  • I encountered the problem (solution yet to find) that the service was canceled and restarted with different parameters all while there were still partial results in the loop (`Platform.runLater(...)`). The results of the current run and the previous mixed. I guess I need to include some value to check if the incoming results are still valid. – javaj Mar 07 '17 at 14:20