javajavafxconcurrencyjava-threads

JavaFX TaskWorker will not start


I am developing a small JavFXML desktop application that includes a TaskWorker. The objective is to run the task worker when a button is clicked, but nothing happens and the taskworker is not called. The mouse click code is this:

public class TableViewController implements Initializable{
    ....

 @FXML private void handleGoButton (ActionEvent event) {
    TaskWorker taskworker = new TaskWorker(50);  
    new Thread(taskworker).start(); 
  }

The original objective was to run a progress indicator and file copying statements inside the taskworker, but to try and understand the issue I have stripped out most of this code and replaced it with a simple loop that prints out the value of an index. The taskworker thus looks like this:

public class TaskWorker extends Task<Void> {
  public int value;  
  
  public TaskWorker (int value) {
    this.value = value;
    System.out.println("Value = " + this.value );
  }
  
  @Override
  protected Void call() throws Exception {
     
    
    for (int i=0; i < this.value; i++){
      System.out.println("Value of i = " + i );   
      Thread.sleep(400); 
    }
    return null;
  }
}

On a mouse click, I would have expected output confirming that the constructor in TaskWorker got the value that was passed and the output showing the value of the index up an to the value passed. The Taskworker does get the value in the constructor:

compile:
run:
Value = 50
BUILD SUCCESSFUL (total time: 18 seconds)

but the call() method never gets started.


Solution

  • Not enough info

    I do not know that you have given us enough info to diagnose your problem.

    You claim that "Value = 50" output proves your TaskWorker is being constructed and therefore proves that handleGoButton is hooked up to your FXML file. But you do not provide a MCVE, so we cannot verify your assertions.

    Initializable?

    I do not know if this affects your problem, but I wonder about your TableViewController implements Initializable. The Javadoc for Initializable says that interface is outmoded:

    NOTE This interface has been superseded by automatic injection of location and resources properties into the controller. FXMLLoader will now automatically call any suitably annotated no-arg initialize() method defined by the controller. It is recommended that the injection approach be used whenever possible.

    Notice that my controller in example below does not extend any class nor implement any interface (at least not explicitly — I am not clear on what magic JavaFX may perform behind the scenes implicitly).

    Example App

    screenshot of example app with a progress bar in action

    I am no expert on JavaFX, but I did read the page 1 Concurrency in JavaFX of the JavaFX 8 manual. Presumably much of that still applies to JavaFX 23.

    From that reading I constructed a complete example app that may be of interest to you.

    You said:

    The original objective was to run a progress indicator and file copying statements inside the taskworker

    The documentation provides an example of ProgressBar doing just what you want. I used that doc in building the example below.

    I began by creating a JavaFX project using the new-project template provided by IntelliJ 2024.3. Then I modified the code just enough to add the ProgressBar, and to rename variables descriptively. Still some references to the original template’s "Hello" remain, but disregard.

    Your code uses new Thread. Two problems there:

    So here I established an ExecutorService in my subclass of Application. Unfortunately I do not usually use FXML nor controllers. So I do not know how to gracefully communicate the ExecutorService object from the Application subclass (where it needs to be initialized and eventually shutdown during the lifecycle hooks for our app) to the controller (where it needs to be used). As a hack, I made the ExecutorService object static. Hopefully you know of a better way other than static.

    This app is not meant to be production-ready. It is not even meant to be an ideal example of proper JavaFX programming. I am not at all a JavaFX expert. I only mean to inspire you to continue onward, and point you in what is hopefully the right direction. And maybe attract some critical attention from folks who are JavaFX experts.

    My FXML.

    <?xml version="1.0" encoding="UTF-8"?>
    
    <?import javafx.geometry.Insets?>
    <?import javafx.scene.control.Label?>
    <?import javafx.scene.layout.VBox?>
    
    <?import javafx.scene.control.Button?>
    <?import javafx.scene.control.ProgressBar?>
    <VBox alignment="CENTER"
          spacing="20.0"
          xmlns:fx="http://javafx.com/fxml"
          fx:controller="work.basil.example.exfxtask.HelloController">
        <padding>
            <Insets bottom="20.0"
                    left="20.0"
                    right="20.0"
                    top="20.0"/>
        </padding>
    
        <Label fx:id="messageText"/>
        <Button fx:id="startButton"
                text="Start"
                onAction="#onStartButtonClicked"/>
        <ProgressBar fx:id="fileProgress"/>
    </VBox>
    

    My controller.

    package work.basil.example.exfxtask;
    
    import javafx.application.Platform;
    import javafx.concurrent.Task;
    import javafx.fxml.FXML;
    import javafx.scene.control.Button;
    import javafx.scene.control.Label;
    import javafx.scene.control.ProgressBar;
    
    import java.time.Duration;
    import java.time.ZonedDateTime;
    
    public class HelloController
    {
        @FXML
        private Label messageText;
        @FXML
        public Button startButton;
        @FXML
        public ProgressBar fileProgress;
    
        @FXML
        protected void onStartButtonClicked ( )
        {
            messageText.setText( "Started " + ZonedDateTime.now( ).toString( ) );
            startButton.setDisable( true );
    
            Task < Void > task = new Task <>( )
            {
                @Override
                public Void call ( )
                {
    
                    final int max = 100;
                    for ( int i = 1 ; i <= max ; i++ )
                    {
                        try { Thread.sleep( Duration.ofMillis( 100 ) ); } catch ( InterruptedException e ) { throw new RuntimeException( e ); }
                        if ( isCancelled( ) )
                        {
                            break;
                        }
                        updateProgress( i , max );
                    }
                    Platform.runLater( new Runnable( )
                    {
                        @Override
                        public void run ( )
                        {
                            messageText.setText( "Finished " + ZonedDateTime.now( ).toString( ) );
                            startButton.setDisable( false );
                        }
                    } );
                    return null;
                }
            };
            fileProgress.progressProperty( ).bind( task.progressProperty( ) );
            HelloApplication.executorService.submit( task );
    
        }
    }
    

    My Application subclass.

    package work.basil.example.exfxtask;
    
    import javafx.application.Application;
    import javafx.fxml.FXMLLoader;
    import javafx.scene.Scene;
    import javafx.stage.Stage;
    
    import java.io.IOException;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class HelloApplication extends Application
    {
        public static final ExecutorService executorService = Executors.newCachedThreadPool( );
    
        @Override
        public void init ( ) throws Exception
        {
            super.init( );
        }
    
        @Override
        public void start ( Stage stage ) throws IOException
        {
            FXMLLoader fxmlLoader = new FXMLLoader( HelloApplication.class.getResource( "hello-view.fxml" ) );
            Scene scene = new Scene( fxmlLoader.load( ) , 400 , 240 );
            stage.setTitle( "Hello!" );
            stage.setScene( scene );
            stage.show( );
        }
    
        @Override
        public void stop ( ) throws Exception
        {
            super.stop( );
            HelloApplication.executorService.shutdown( );
        }
    
        public static void main ( String[] args )
        {
            launch( );
        }
    }
    

    Here is my POM.

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>work.basil.example</groupId>
        <artifactId>ExFxTask</artifactId>
        <version>1.0-SNAPSHOT</version>
        <name>ExFxTask</name>
    
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        </properties>
    
        <dependencies>
    
            <!-- https://mvnrepository.com/artifact/org.openjfx/javafx-controls -->
            <dependency>
                <groupId>org.openjfx</groupId>
                <artifactId>javafx-controls</artifactId>
                <version>23</version>
                <scope>provided</scope>
            </dependency>
    
       <!-- https://mvnrepository.com/artifact/org.openjfx/javafx-fxml -->
            <dependency>
                <groupId>org.openjfx</groupId>
                <artifactId>javafx-fxml</artifactId>
                <version>23</version>
                <scope>provided</scope>
            </dependency>
    
            <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
            <dependency>
                <groupId>org.junit.jupiter</groupId>
                <artifactId>junit-jupiter-api</artifactId>
                <version>5.11.1</version>
                <scope>test</scope>
            </dependency>
    
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.13.0</version>
                    <configuration>
                        <source>23</source>
                        <target>23</target>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.openjfx</groupId>
                    <artifactId>javafx-maven-plugin</artifactId>
                    <version>0.0.8</version>
                    <executions>
                        <execution>
                            <!-- Default configuration for running with: mvn clean javafx:run -->
                            <id>default-cli</id>
                            <configuration>
                                <mainClass>work.basil.example.exfxtask/work.basil.example.exfxtask.HelloApplication</mainClass>
                                <launcher>app</launcher>
                                <jlinkZipName>app</jlinkZipName>
                                <jlinkImageName>app</jlinkImageName>
                                <noManPages>true</noManPages>
                                <stripDebug>true</stripDebug>
                                <noHeaderFiles>true</noHeaderFiles>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </project>