JavaFx new Thread with same Task as input

John Szatmari Source

I am working on a JavaFX desktop application and I have one button that should read from the memory of an embedded device and print that into a JSON. I have implemented a Task that does that, and this Task is passed as argument to a new thread in the button event handler. The problem is, this only works once. After that, even though new threads are generated on button click, the call() method of the Task is never called again. Here is the code:

The Task definition:

Task readValDaemon = new Task<Void>() {
    @Override
    public Void call() {
        //This functions reads from memory and writes the JSON
        readDataHI(connection,commandListHI,statusHI);
        return null;
    }
};

The Thread creation:

readData.setOnMouseClicked(new EventHandler<MouseEvent>() {
    @Override
    public void handle(MouseEvent event) {

        Thread readValThread = new Thread(readValDaemon);
        readValThread.setDaemon(true);
        readValThread.start();
    }
});
javajsonmultithreadingjavafxtask

Answers

answered 6 months ago Michael #1

Task is kind of the wrong tool for this. It's very purposefully only designed to run once because it's a kind of future. It stores the result (in your case null) as a kind of memoization to avoid doing expensive operations more times than is necessary. So Task is best suited for situations where an expensive computation must be done just once, and usually you would want a result from it at some point down the line.

The documentation for Task is very thorough so I would give that a read.

In your case, just use a plain Runnable. You can use a lambda expression:

readData.setOnMouseClicked(new EventHandler<MouseEvent>() {
    @Override
    public void handle(MouseEvent event)
    {
        Thread readValThread = new Thread(() -> readDataHI(a, b, c));
        readValThread.setDaemon(true);
        readValThread.start();
    }
});

As an aside, creating threads manually isn't considered very good practice in modern Java. Strongly consider an ExecutorService instead.

answered 6 months ago James_D #2

As observed in other answers, a Task is an implementation of FutureTask. From the Task documentation:

As with FutureTask, a Task is a one-shot class and cannot be reused. See Service for a reusable Worker.

So you cannot reuse a task. Second and subsequent attempts to run it will just silently fail.

You could just create a new task directly every time:

private Task<Void> createReadValTask() {
    return new Task<Void>() { 
        @Override
        public Void call() {
            //This functions reads from memory and writes the JSON
            readDataHI(connection,commandListHI,statusHI);
            return null;
        }
    };
}

and then do

readData.setOnMouseClicked(new EventHandler<MouseEvent>() {
    @Override
    public void handle(MouseEvent event) {

        Thread readValThread = new Thread(createReadValTask());
        readValThread.setDaemon(true);
        readValThread.start();
    }
});

You could also consider using a Service, which is designed for reuse. It basically encapsulates the "create a new task every time" functionality, but adds in a lot of useful UI callbacks. A Service also manages a thread pool for you (via an Executor), so you no longer need to worry that you may be creating too many thread. (The Executor can also be specified, if you want to control it.)

So, e.g.:

Service<Void> readValDaemon = new Service<Void>() {
    @Override
    protected Task<Void> createTask() {
        return new Task<Void>() {
            @Override
            public Void call() {
                //This functions reads from memory and writes the JSON
                readDataHI(connection,commandListHI,statusHI);
                return null;
            }
        };
    }
};

and then

readData.setOnMouseClicked(new EventHandler<MouseEvent>() {
    @Override
    public void handle(MouseEvent event) {
        readValThread.restart();
    }
});

If the mouse is clicked while the service is already running, this will automatically cancel the already running task, and restart a new one. You could add in checks if you wanted, or bind the disable state of readData to the state of the Service, if you wanted.

comments powered by Disqus