1

When I need to do an indeterminate number of pieces of work in the JavaFX thread without blocking the user interface, I use this class

public class AsyncWhile {

    private final IntPredicate hook;
    private int schedCount = 0;
    private boolean terminated = false;
    private int callCount = 0;
    private static final int schedN = 1;

    public AsyncWhile(IntPredicate hook) {
        this.hook = hook;
        schedule();
    }

    public void kill(){
        terminated = true;
    }

    private void schedule(){
        while(schedCount < schedN){
            Platform.runLater(this::poll);
            schedCount++;
        }
    }

    private void poll(){
        schedCount--;
        if(!terminated){
            terminated = !hook.test(callCount++);
            if(!terminated){
                schedule();
            }
        }
    }
}

like this

asyncWhile = new AsyncWhile(i -> {
    // return false when you're done
    //     or true if you want to be called again
});

// can asyncWhile.kill() should we need to

(

If you need a more concrete example, here I'm reading one line at a time from an InputStream and then parsing and displaying a plot parsed from that line:

asyncWhile = new AsyncWhile(i -> {
    String line;
    try {
        if((line = reader.readLine()).startsWith(" Search complete.")){ // it so happens that this reader must be read in the JavaFX thread, because it automatically updates a console window
            return false;
        } else {
            Task<MatchPlot> task = new ParsePlotTask(line);
            task.setOnSucceeded(wse -> {
                plotConsumer.accept(task.getValue());
                // todo update progress bar
            });
            executorService.submit(task);
            return true;
        }
    } catch (IOException ex) {
        new ExceptionDialog(ex).showAndWait();
        return false;
    }
});

)


Chaining up runLaters like that feels like a hack. What is the proper way to solve this kind of problem? (By "this kind of problem" I mean the problem that would have been solved by a simple while loop, had it not been for the fact that its contents must run in the JavaFX thread without making the UI unresponsive.)

Museful
  • 6,711
  • 5
  • 42
  • 68
  • 1
    I'm not quite sure how to answer your question: I would solve the example problem you describe at the end with something like the `PartialResultsTask` example from the [`Task` documentation](http://docs.oracle.com/javase/8/javafx/api/javafx/concurrent/Task.html). But note that you have subtle threading bugs in your `AsyncWhile` class: `terminated` needs to be declared `volatile` as it is accessed from multiple threads, and `schedCount` needs either to be accessed in `synchronized` blocks, or needs to be replaced by an `AtomicInteger`. Seems like you are trying to reinvent the wheel though... – James_D Jan 14 '16 at 00:11
  • @James_D How can `terminated` be accessed from multiple threads if all the code listed runs in the JavaFX thread? – Museful Jan 14 '16 at 09:28
  • You have to assume `kill()` is only ever called from the FX Application Thread. Your code doesn't enforce that, but if that's true then `terminated` doesn't need to be `volatile`. Given how generally you have written this class though, and given `kill()` is unlikely to be a performance-critical operation, you should probably make that thread-safe. – James_D Jan 14 '16 at 11:42
  • @James_D It's a single-thread class. If there are any subtle bugs in it as such, I would be grateful to know about them. – Museful Jan 14 '16 at 12:23
  • OK, I misunderstood this then. I assumed (since you had calls to `Platform.runLater(...)`) this was intended to be used from a background thread. – James_D Jan 14 '16 at 12:35

1 Answers1

1

Recommended

In general, basing a solution off of the PartialResultsTask sample from the Task documentation (which relies on Platform.runLater invocations), is the standard way of solving this problem.

Alternate

Rather than scheduling runLater's you could use a BlockingDeque. In your processing task, you perform your time-consuming process just with a normal while loop, generate non-UI model objects which need to be represented in the JavaFX UI, stick those non-UI model objects into your queue. Then you setup a Timeline or AnimationTimer that polls the queue, draining it as necessary and to pick the items off the queue and represent them in the UI.

This approach is similar (but a bit different) to: Most efficient way to log messages to JavaFX TextArea via threads with simple custom logging frameworks.

Using your own queue in this case is not much different from using the implicit queue runLater invocations go on to, though, with your own queue, you might have a little more control over the process if you need that. It's a trade-off though, as it adds a bit more custom code and complexity, so probably just use the recommended PartialResults sample from Task and, if that doesn't fit your needs, then perhaps investigate the alternative custom queue based approach.

Aside

As a side note, you could use the custom logging framework linked earlier to log console messages from multiple threads to be displayed in your UI. That way you don't need to have your reader.readLine call execute I/O on the JavaFX UI, which is not recommended. Instead, have the I/O performed off the JavaFX UI thread and, as you process items, call into the logging framework to log messages that will eventually show up on the UI (the internal mechanisms within the logging framework take care of ensuring that JavaFX threading rules are respected).

Can you see any danger in using my approach?

Sorry for being non-specific here. I'm not going to directly answer this, but tangentially and not always applicably to your approach, using runLater can cause issues, mostly it is not a concern, but some things to consider:

  1. If you send enough runLater calls faster than they can be processed, eventually you will either run out of memory or some runLater calls will start being ignored (depending on how the runLater system works).
  2. Calls to runLater are sequential, not prioritized, so if there are internal events which are also being runLater, such as handling UI events, those might be delayed while your runLater calls are being processed.
  3. runLater offers no guarantee of when later is. If your work is time sensitive, that might be an issue or at least something you need to account for in your implementation.
  4. The runLater system is likely internally fairly complex and you won't know exactly how it is implemented unless you study the source code pretty closely.
  5. Anything that you run on runLater is going to hold up the JavaFX application thread, probably until all of the outstanding runLater calls are complete
  6. Once you have issued a bunch of runLater calls, you can't easily intersperse their processing over multiple pulses in the JavaFX animation system, they will likely all be executed on the next pulse. So you have to be careful not to send too many calls at once.

Those are just some things that come to mind.

In general though, runLater is a sound mechanism for many tasks and a core part of the JavaFX architecture. For most things the above considerations don't really have any consequence.

Writing quality multi-threaded code is pretty tricky. To the point where it often best avoided where possible, which is what the JavaFX system attempts to do for the most part by making scene graph access single-threaded. If you must do it, then stick to the patterns outlined in the Task documentation or utilizing some of the high level java.util.concurrent systems as much as possible rather than implementing your own systems. Also note that reading multi-threaded code is even trickier than writing it, so make sure what you do is clear to the next person.

Community
  • 1
  • 1
jewelsea
  • 150,031
  • 14
  • 366
  • 406
  • Thank you I will look at these options. I had considered a `Timeline`/`AnimationTimer` but the frequency of callbacks should adapt to processor availability (to the JavaFX thread). Can you see any danger in using my approach? (What I fear (rationally or not) is that if for some or other reason a `runLater` doesn't happen, then the whole chain breaks. This is why I would consider `schedN>1`, so that the chain has multiple independent links everywhere.) – Museful Jan 14 '16 at 09:42
  • Can you elaborate on "the frequency of callbacks should adapt to processor availability to the FX Application Thread"? The way I understand that, `AnimationTimer` gives you exactly that functionality. – James_D Jan 14 '16 at 12:14
  • Ah.. I said that because I had assumed those would both do fixed-interval callbacks. By scheduling one `runLater` at a time I assume it (and and not the whole bunch) will be called after whatever the thread currently wants to do is done. `AsyncWhile` seems to work really well in practice so far. Besides the UI remaining responsive, even the time-to-completion is much faster than with a normal while loop, for some strange reason. I appreciate all your insights and pointers, which I will still be looking at more carefully. – Museful Jan 14 '16 at 12:41