Search code examples
javamultithreadinguser-interfacejavafxcountdownlatch

Updating two or more ProgressBar with countDownLatch


I'm learning multithreads with progressbar. I have got update without problems in the following simple code:

public class Controller implements Initializable {
@FXML
private Button id_boton1;
@FXML
private Button id_boton2;

@FXML
private ProgressBar id_progressbar1;
@FXML
private ProgressIndicator id_progressindicator1;

@Override
public void initialize(URL location, ResourceBundle resources) {
    id_progressbar1.setProgress(0.0);
    id_boton1.setOnAction(new EventHandler<ActionEvent>() {
        @Override
        public void handle(ActionEvent event) {
            Thread hiloProgressBar= new Thread(new bg_Thread1());
            //hilo.setDaemon(true);
            hiloProgressBar.start();
        }
    });

    id_boton2.setOnAction(new EventHandler<ActionEvent>() {
        @Override
        public void handle(ActionEvent event) {
            Thread hiloProgressIndicate = new Thread(new bg_Thread2());
            hiloProgressIndicate.start();
        }
    });



}

class bg_Thread1 implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            id_progressbar1.setProgress((float)i/99);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(i);

        }
    }
}

class bg_Thread2 implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            id_progressindicator1.setProgress((float)i/99);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(i);

        }
    }
}}

Nothing special. 2 buttons that create thread with a job and progressBar.

My question is about the implementation of CountDownLatch to this program. I want to wait for the two threads to finish. Now the UI does not refresh with the progressbar changes

public class Controller implements Initializable {
@FXML
private Button id_boton1;


@FXML
private ProgressBar id_progressbar1;
@FXML
private ProgressIndicator id_progressindicator1;

@Override
public void initialize(URL location, ResourceBundle resources) {



    CountDownLatch countDownLatch = new CountDownLatch(2);

    id_progressbar1.setProgress(0.0);
    id_boton1.setOnAction(new EventHandler<ActionEvent>() {
        @Override
        public void handle(ActionEvent event) {

            System.out.println("iniciando Threads");

            Thread hiloProgressBar= new Thread(new bg_Thread1(countDownLatch));
            hiloProgressBar.start();

            Thread hiloProgressIndicate = new Thread(new bg_Thread2(countDownLatch));
            hiloProgressIndicate.start();

            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("*fin*");
        }
    });


}

class bg_Thread1 extends Thread{

    private CountDownLatch latch;

    public bg_Thread1(CountDownLatch latch) {
        this.latch = latch;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            id_progressbar1.setProgress((float)i/99);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(i);

        }
        latch.countDown();
    }
}

class bg_Thread2 extends Thread{

    private CountDownLatch latch;

    public bg_Thread2(CountDownLatch latch) {
        this.latch = latch;
    }

    @Override
    public void run() {
        for (int i = 0; i < 40; i++) {
            id_progressindicator1.setProgress((float)i/39);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(i);

        }
        latch.countDown();
    }
}}

Now, One button start two threads, when the work is finish the progressBar is updated one time


Solution

  • A CountDownLatch is good for example when you want to divide a SQL query up into smaller queries, and wait for all the queries to come back. Then combine your results only after all of them have finished. I'm not sure if you really need it or not... you can just add an event handler when the state has "succeeded". For this example, I changed a label to "Finished" once the respective thread had completed. If you need to wait for all of them to finish to do something else, then you would need to encapsulate the latch in yet another thread. latch.await() if run on the UI thread will freeze the UI.

    Update: I've implemented a CountDownLatch since you need to wait for both threads to complete to do something else.


    enter image description here enter image description here enter image description here enter image description here


    import java.util.concurrent.CountDownLatch;
    import javafx.application.Application;
    import javafx.concurrent.Task;
    import javafx.concurrent.WorkerStateEvent;
    import javafx.event.ActionEvent;
    import javafx.geometry.Pos;
    import javafx.scene.Scene;
    import javafx.scene.control.Button;
    import javafx.scene.control.Label;
    import javafx.scene.control.ProgressBar;
    import javafx.scene.control.ProgressIndicator;
    import javafx.scene.layout.VBox;
    import javafx.stage.*;
    
    public class ProgressTest extends Application
    {
    
        /**
         * @param args the command line arguments
         */
        public static void main(String[] args)
        {
            launch(args);
        }
    
        @Override
        public void start(Stage primaryStage)
        {
    
            Button getButton = new Button("Copy");
    
            ProgressBar progressBar = new ProgressBar(0);
            ProgressIndicator progressIndicator = new ProgressIndicator(0);
            progressBar.setProgress(0);
            progressIndicator.setProgress(0);
            Label statusLabel1 = new Label();
            Label statusLabel2 = new Label();
            VBox vBox = new VBox();
            vBox.getChildren().addAll(statusLabel1, statusLabel2, progressBar, progressIndicator, getButton);
            vBox.setAlignment(Pos.CENTER);
    
            getButton.setOnAction((ActionEvent e) ->
            {
    
                CountDownLatch countDownLatch = new CountDownLatch(2);
    
                // Create a Task.
                CopyTask copyTask1 = new CopyTask(30, countDownLatch);
                CopyTask copyTask2 = new CopyTask(50, countDownLatch);
                progressBar.progressProperty().unbind();
                // Bind progress property
                progressBar.progressProperty().bind(copyTask2.progressProperty());
    
                progressIndicator.progressProperty().unbind();
                // Bind progress property.
                progressIndicator.progressProperty().bind(copyTask1.progressProperty());
    
                // Unbind text property for Label.
                statusLabel1.textProperty().unbind();
                statusLabel2.textProperty().unbind();
    
                // Bind the text property of Label
                // with message property of Task
                statusLabel1.textProperty().bind(copyTask1.messageProperty());
                statusLabel2.textProperty().bind(copyTask2.messageProperty());
    
                // When completed tasks
                copyTask1.addEventHandler(WorkerStateEvent.WORKER_STATE_SUCCEEDED, (WorkerStateEvent t) ->
                {
                    statusLabel1.textProperty().unbind();
                    statusLabel1.setText("Finished1");
                });
                copyTask2.addEventHandler(WorkerStateEvent.WORKER_STATE_SUCCEEDED, (WorkerStateEvent t) ->
                {
                    statusLabel2.textProperty().unbind();
                    statusLabel2.setText("Finished2");
    
                });
    
                Task<Void> task = new Task<Void>()
                {
                    @Override
                    public Void call() throws InterruptedException
                    {
    
                        // Start the Task.
                        new Thread(copyTask1).start();
                        new Thread(copyTask2).start();
                        countDownLatch.await();
                        return null;
    
                    }
                };
    
                task.setOnSucceeded(event ->
                {
                    System.out.println("This occurs after both threads complete...");
    
                });
    
                task.setOnFailed(event ->
                {
                    System.out.println("FAIL...");
                });
    
                Thread thread = new Thread(task);
                thread.start();
    
            });
    
            final Scene scene = new Scene(vBox);
    
            primaryStage.setScene(scene);
    
            primaryStage.show();
        }
    
        public class CopyTask extends Task<Void>
        {
    
            int x;
            CountDownLatch latch;
    
            public CopyTask(int x, CountDownLatch latch)
            {
                super();
                this.x = x;
                this.latch = latch;
    
            }
    
            @Override
            protected Void call() throws Exception
            {
                //don't do the infitnite progress meter ...
                this.updateProgress(0, x);
    
                try
                {
    
                    int len;
                    for (len = 0; len <= x; len++)
                    {
                        this.updateProgress(len, x);
                        Thread.sleep(100);
                        if (this.isCancelled())
                        {
                            throw new InterruptedException();
                        }
                    }
    
                    if (this.isCancelled())
                    {
                        throw new InterruptedException();
                    }
                } catch (InterruptedException ex)
                {
                    System.out.println("Cancelled");
                }
                latch.countDown();
                return null;
    
            }
    
        }
    
    }