Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Task#call() method invoked before task is executed

According to the documentation, Task#call() is "invoked when the Task is executed ". Consider the following program:

import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.stage.Stage;

public class TestTask extends Application {

    Long start;

    public void start(Stage stage) {

        start = System.currentTimeMillis();

        new Thread(new Taskus()).start(); 
    }

    public static void main(String[] args) {
        launch();
    }

    class Taskus extends Task<Void> {

        public Taskus() {
            stateProperty().addListener((obs, oldValue, newValue) -> {
                try {
                    System.out.println(newValue + " at " + (System.currentTimeMillis()-start));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }

        public Void call() throws InterruptedException {

            for (int i = 0; i < 10000; i++) {
                // Could be a lot longer.
            }
            System.out.println("Some code already executed." + " at " + (System.currentTimeMillis()-start));

            Thread.sleep(3000);

            return null;
        }
    }
}

Executing this program gives me the following output:

Some code already executed. after 5 milliseconds
SCHEDULED after 5 milliseconds
RUNNING after 7 milliseconds
SUCCEEDED after 3005 milliseconds

Why is the call() method invoked before the task is even scheduled? This makes no sense to me. In the task where I first saw the issue my task executed a few seconds before the task went into the SCHEDULED state. What if I want to give the user some feedback on the state, and nothing happens until the task has already been executed for a few seconds?

like image 296
Jonatan Stenbacka Avatar asked Sep 08 '25 10:09

Jonatan Stenbacka


1 Answers

Why is the call() method invoked before the task is even scheduled?

TLDR; version: It's not. It's merely invoked before you get notified that it's been scheduled.


You have two threads running, essentially independently: the thread you explicitly create, and the FX Application Thread. When you start your application thread, it will invoke Taskus.call() on that thread. However, changes to the the task's properties are made on the FX Application Thread via calls to Platform.runLater(...).

So when you call start() on your thread, the following occurs behind the scenes:

  1. A new thread is started
  2. On that thread, an internal call() method in Task is called. That method:
  3. Schedules a runnable to execute on the FX Application Thread, that changes the stateProperty of the task to SCHEDULED
  4. Schedules a runnable to execute on the FX Application Thread, that changes the stateProperty of the task to RUNNING
  5. Invokes your call method

When the FX Application Thread receives the runnable that changes the state of the task from READY to SCHEDULED, and later from SCHEDULED to RUNNING, it effects those changes and notifies any listeners. Since this is on a different thread to the code in your call method, there is no "happens-before" relationship between code in your call method and code in your stateProperty listeners. In other words, there is no guarantee as to which will happen first. In particular, if the FX Application Thread is already busy doing something (rendering the UI, processing user input, processing other Runnables passed to Platform.runLater(...), etc), it will finish those before it makes the changes to the task's stateProperty.

What you are guaranteed is that the changes to SCHEDULED and to RUNNING will be scheduled on the FX Application thread (but not necessarily executed) before your call method is invoked, and that the change to SCHEDULED will be executed before the change to RUNNING is executed.

Here's an analogy. Suppose I take requests from customers to write software. Think of my workflow as the background thread. Suppose I have an admin assistant who communicates with the customers for me. Think of her workflow as the FX Application thread. So when I receive a request from a customer, I tell my admin assistant to email the customer and notify them I received the request (SCHEDULED). My admin assistant dutifully puts that on her "to-do" list. A short while later, I tell my admin assistant to email the customer telling them I have started working on their project (RUNNING), and she adds that to her "to-do" list. I then start working on the project. I do a little work on the project, and then go onto Twitter and post a tweet (your System.out.println("Some code already executed")) "Working on a project for xxx, it's really interesting!". Depending on the number of things already on my assistant's "to-do" list, it's perfectly possible the tweet may appear before she sends the emails to the customer, and so perfectly possible the customer sees that I have started work on the project before seeing the email saying the work is scheduled, even though from the perspective of my workflow, everything occurred in the correct order.

This is typically what you want: the status property is designed to be used to update the UI, so it must run on the FX Application Thread. Since you are running your task on a different thread, you presumably want it to do just that: run in a different thread of execution.

It seems unlikely to me that a change to the scheduled state would be observed a significant amount of time (more than one frame rendering pulse, typically 1/60th second) after the call method actually started executing: if this is happening you are likely blocking the FX Application thread somewhere to prevent it from seeing those changes. In your example, the time delay is clearly minimal (less than a millisecond).

If you want to do something when the task starts, but don't care which thread you do it on, just do that at the beginning of the call method. (In terms of the analogy above, this would be the equivalent of me sending the emails to the customer, instead of requesting that my assistant do it.)

If you really need code in your call method to happen after some user notification has occurred on the FX Application Thread, you need to use the following pattern:

public class Taskus extends Task<Void> {

    @Override
    public Void call() throws Exception {
        FutureTask<Void> uiUpdate = new FutureTask<Void>(() -> {
            System.out.println("Task has started");
            // do some UI update here...
            return null ;
        });
        Platform.runLater(uiUpdate);
        // wait for update:
        uiUpdate.get();
        for (int i = 0; i < 10000; i++) {
            // any VM implementation worth using is going 
            // to ignore this loop, by the way...
        }
        System.out.println("Some code already executed." + " at " + (System.currentTimeMillis()-start));
        Thread.sleep(3000);
        return null ;
    }
}

In this example, you are guaranteed to see "Task has started" before you see "Some code already executed". Additionally, since displaying the "Task has started" method happens on the same thread (the FX Application thread) as the changes in state to SCHEDULED and RUNNING, and since displaying the "Task has started" message is scheduled after those changes in state, you are guaranteed to see the transitions to SCHEDULED and RUNNING before you see the "Task has started" message. (In terms of the analogy, this is the same as me asking my assistant to send the emails, and then not starting any work until I know she has sent them.)

Also note that if you replace your original call to

System.out.println("Some code already executed." + " at " + (System.currentTimeMillis()-start));

with

Platform.runLater(() -> 
    System.out.println("Some code already executed." + " at " + (System.currentTimeMillis()-start)));

then you are also guaranteed to see the calls in the order you are expecting:

SCHEDULED after 5 milliseconds
RUNNING after 7 milliseconds
Some code already executed. after 8 milliseconds
SUCCEEDED after 3008 milliseconds

This last version is the equivalent in the analogy of me asking my assistant to post the tweet for me.

like image 124
James_D Avatar answered Sep 11 '25 22:09

James_D