0

Can I run Service in background thread?

I want to load data from database and during loading data i want progress bar to be indicating progress. First I have created task, run it in background thread and then update progress bar according to this task. However, I found out that this Task is not reusable, so next time i hit the button, it didn't work. Later on, I fount out that Service can run Tasks multiple times, So i have encapsulated those tasks inside of Service.

Now it works great - every time i hit button, table is reloaded - except progress bar is not moving.

How can I reach desired result?

Mr. Crow
  • 27
  • 12
  • The progressbar is a UI component, and so changes to it's state must be made on the JavaFX Application thread. Try wrapping your calls to update the progress bar in a Platform.runLater() and see if that works. – MMAdams May 16 '18 at 15:41
  • 1
    @MMAdams No necessary need to wrap the call to `Platform.runLater()` as Task has a [method](https://docs.oracle.com/javase/8/javafx/api/javafx/concurrent/Task.html#updateProgress-double-double-) `updateProgress` method. Just need to bind the `progressProperty` of your task/service with the `progressBar` in the JavaFX thread. – Pagbo May 16 '18 at 16:48
  • @Pagbo thank you! I knew I was forgetting something specifically relating to ProgressBars! – MMAdams May 16 '18 at 16:49
  • It doesnt works guys. This is body of my runService() method: public void runService(Service service){ service.start(); progressBar.progressProperty().bind(service.progressProperty()); service.setOnSucceeded((event) -> { try { Thread.sleep(5000);//to give impression of long running } catch (InterruptedException ex) { // } service.reset(); }); } – Mr. Crow May 17 '18 at 05:39
  • In Service definition, there is a task wrapped and in that task i have code like updateProgress(-1,1) to set it to undeterminated state and then there is a method for loading data. after loading i have updatePorgress(1, 1) to set it completed. Then i bind this to pregress bar in my runService(Service service) method. How ever it get laggy and what's more, on service completition, progress bar is in indeterminated state (it should be copmleted) – Mr. Crow May 17 '18 at 05:42
  • 1
    ok, i got this, something must be wrong, i wrote different program, more simple to play with services and progress bar in UI and it works. Gonna investigate the problem – Mr. Crow May 17 '18 at 05:59
  • @Mr.Crow If you encounter problems while investigation, consider to ask another question with [mcve](https://stackoverflow.com/help/mcve), this will help people to help you :). – Pagbo May 17 '18 at 07:25

1 Answers1

1

Okay... Here is completed code: https://mega.nz/#!yUsjgJyZ!DHfuBqsujAHurS-pQ_W5y8BAflOtvxsm48goRPkDsxA

First, i want to tell you what is my goal:

When I run program, i want the progress bar to be in indeterminate state. After pressing the button, i want progress bar to reflect progress of Service. After finishing the Service, i want the progress bar to be completed (100%).

Here is what i have found. It looks like service automatically runs it's task on background thread. I have built a "sand box" program where i was playing with services and progress bar. The program comprised of progress bar, two text fields with two buttons above them. First button can start service_countTo100 and second button can run service_getActualCount.

Now in fxml file i set progress bar to indeterminate default state. After pressing the button1, a counting has started (displayed in text_field1) and progress bar has changed according to actual progress. After pressing button2, actual value of count is displayed in text_field2. And here comes some issues. To demonstrate the issues, i will show you source code of Service:

// Create the service
public static Service<Integer> serviceTo100 = new Service<Integer>() {
    @Override
    protected Task<Integer> createTask() {
        Task<Integer> taskTo100 = new Task<Integer>() {
            @Override protected Integer call() throws Exception {

                int iterations;
                updateProgress(-1, 100);
                for (iterations = 0; iterations < 100; iterations++) {
                    if (isCancelled()) {
                        updateMessage("Cancelled");
                        break;
                    }

                    updateMessage("Iteration " + iterations);
                    updateProgress(iterations, 100);

                    // Now block the thread for a short time, but be sure
                    // to check the interrupted exception for cancellation!
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException interrupted) {
                        if (isCancelled()) {
                            updateMessage("Cancelled");
                            break;
                        }
                    }
                }

            //udpateProgress(100, 100);    
            return iterations;
            }
        };
    return taskTo100;
    }
};

As you can see, there is a simple loop which counts from 0 to 100 and there is also a updateProgress(iterations, 100); statement which updates progress. This service is located in Services class. In FXMLDocumentController.java is method called runService(Service service) defined as this:

public void runService(Service service){

        service.start();
        progressBar.progressProperty().bind(service.progressProperty());

        service.setOnSucceeded((event) -> {
            service.reset();
            progressBar.progressProperty().unbind();
            progressBar.setProgress(1);                                
        });
}

The initialize looks like this:

@Override
public void initialize(URL url, ResourceBundle rb) {
    btn_start1.setOnAction((event) -> {

        Service service = Services.serviceTo100;
        runService(service);
        txt_field1.textProperty().bind(service.messageProperty());                     
        System.out.printf("something\n");  

    });
    .
    .
    .
    }//end of initialize

Now the most interesting thing (at least for me, novice) comes into play.

In service definition, you can notice commented line //updateProgress(100,100). That was my try to set progress bar to completed state when counting has stopped. But java ignored that line. I tried to put System.out.print("Something"); and it worked, but updateProgress(100, 100) didn't. When counting has finished, progress bar was set to it's default state defined in fxml file, which was indeterminate.

Conclusion: Service class creates task defined inside of it and after task is completed, the task doesn't exists anymore, thus, there is no progress to be bound with progress bar.

Even though task does not exists anymore, you cannot set progress of the bounded progress bar with setProgress(double double) method. You first need to unbound it.

Now I got what i needed:

You run the program, progress bar is in indeterminate state, waiting for hit. You hit the button, service starts counting up to 100 and progress bar is progressing according to progressProperty of service. When counting is done, Progress bar is set to indeterminate state again, waiting for another start.

Mr. Crow
  • 27
  • 12
  • Javadoc from the `Worker` interface: _"A Worker which is in the Worker.State.READY or Worker.State.SCHEDULED states will always have workDone and progress set to -1. A Worker which is in the Worker.State.SUCCEEDED state will always have workDone == totalWork and progress == 1. In any other state, the values for these properties may be any value in their respective valid ranges."_. Because `Service` implements `Worker` the progress when complete should be `1.0` (or `100/100`). – Slaw May 17 '18 at 09:51
  • The reason this is not happening in your case is because of your call to `service.reset()` in the `service.setOnSucceeded` `EventHandler`. Calling `reset()` will immediately put the `Service` into the `READY` state and (as the Javadoc of `Worker` states) set the `progress` to indeterminate (`-1`). – Slaw May 17 '18 at 09:53
  • However, looking at the source code of `Task` I don't see any readily obvious enforcement of the _"A Worker which is in the Worker.State.SUCCEEDED state will always have workDone == totalWork and progress == 1."_ part of the contract. This probably means that in practice the progress once `SUCCEEDED` will be whatever you last updated it to be. And because internally all the properties of `Service` are bound to the created `Task` the same will apply to `Service`. – Slaw May 17 '18 at 10:00
  • Thank you Slaw, that's good to know. I am not that good at java, still learning, however your answer helped. So actually i should reset Service before every start rather that after every Succeeded and then i should have progress value of pregress bar at 1, right? – Mr. Crow May 17 '18 at 10:22
  • Yes, if you want the progress to keep reflecting the fact the `Service` has succeeded you must only call `reset()` when you are ready to restart the `Service`. You could also use the [`Service.restart()`](https://docs.oracle.com/javase/10/docs/api/javafx/concurrent/Service.html#restart()) method. – Slaw May 17 '18 at 10:28
  • Thanks, +1 although link is now broken – a55 Apr 24 '22 at 18:24