0

I want my main thread to wait until the background thread completes. But, this makes my UI freeze. I want to achieve this without freezing my UI. Please help me out. I am new to JavaFX.

My Code is ---

waitNotifyClass wait1 = new waitNotifyClass();
private void download(ActionEvent event) throws SQLException, IOException, InterruptedException {
        try (Connection conn = ......getConnection()) {

            /// opening filechooser
            FileChooser fc = new FileChooser();
            FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter("Compressed(zipped) Folder (*.zip)", "*.zip");//Compressed(zipped) Folder (*.gz)", "*.gz"
            fc.getExtensionFilters().add(extFilter);
            String downloadsLocation = System.getProperty("user.home") + "/Downloads/";
            /// get a File file with name as you save in file chooser

  
            file = fc.showSaveDialog(downloadMultiTraceFileButton.getScene().getWindow());
       
            file.mkdirs();

            File sourceFile = file;
            String s = file.getAbsolutePath().substring(0, file.getAbsolutePath().length() - 4);
            destFile = new File(s);
            if (sourceFile.renameTo(destFile)) {
                System.out.println("File renamed successfully");
            } else {
                System.out.println("Failed to rename file");
            }

            /// object of the folder made after renaming
            File theDir = new File(destFile.getAbsolutePath());

            /// Iterating through the slected item of trace files one by one for create a file and then fill it
            Task task = new Task() {
                @Override
                protected Void call() throws Exception {
                    for (String str : traceFileListView.getSelectionModel().getSelectedItems()) {
                     
                        File myFile = new File(theDir.getAbsolutePath() + "\\" + str);
                        /// make new file if myFile doesnt exist
                        if (!myFile.exists()) {
                            myFile.createNewFile();
                        }
                        /// get writer object of FileWriter for writing finally on file from 
                      

                        FileWriter writer = new FileWriter(myFile);
                 
                        /// query for getting content of trace file
                        String querySubPart = "select * from table_name='" + str + "'";
                     
                        PreparedStatement preparedStatement = conn.prepareStatement(querySubPart);
                        
                        ResultSet resultSet = preparedStatement.executeQuery();
                        resultSet.setFetchSize(200);
                        ///  writing into file from resultSet
                        System.out.println("writing before while loop " + Thread.currentThread());
                        while (resultSet.next()) {
                            try {
                                writer.write(resultSet.getString(1));
                                System.out.println(resultSet.getString(1));
                            } catch (IOException ex) {
                                System.out.println("inside try of write");
                            }
                        }
                        writer.close();
                    }
                    wait1.notifyFunction();
                    updateProgress(1, 1);
                    return null;

                }

            };

            System.err.println("Thread created" + Thread.currentThread());
            new Thread(task).start();
            wait1.waitFunction();

            //// closing the window open where download button was there
            ((Stage) generateTraceFileButton.getScene().getWindow()).close();
            /// getting sourcepath folder which is to be zipped
            String sourcePath = theDir.getAbsolutePath();
            // name of zip folder
            String destinationpath = theDir + ".zip";
            System.out.println(destinationpath);

       
            packInZipCode(sourcePath, destinationpath);
          
  

        } catch (SQLException e) {

            System.out.println("Exception caught \n" + e.toString());
            if (e.toString().trim().toUpperCase().contains("----")) {
                ....
            }
        }
    }

I am sure that my UI is freezing because of wait1.waitFunction() because this is making my UI thread wait and freeze the UI. I want to wait for it but without freezing the app. Edited--

why do I want to achieve this?

because If I will not stop my UI thread, it will go ahead and zip my file. but that same file is being used by the background thread also. So I won't get the desired output. Also, my main task is to do the downloading part(here it is heavy, so take time) in the background thread. At the same time, I don't want my main thread to freeze.

I tried this---> Implemented task.setOnSucceded just after thread creation so that my JavaFx thread will execute this part only after task is completed. But in this case , background thread(task is executing here) is losing connection . Got this error--> java.sql.SQLException: Connection is closed at this line--> preparedStatement = conn.prepareStatement(querySubPart);

My new Code looks like this -->

private void downloadMultiTraceFiles(ActionEvent event) throws SQLException, IOException, InterruptedException {
        try (Connection conn = mainApp.getHikariDataSource().getConnection()) {

            /// opening filechooser
            FileChooser fc = new FileChooser();
            FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter("Compressed(zipped) Folder (*.zip)", "*.zip");//Compressed(zipped) Folder (*.gz)", "*.gz"
            fc.getExtensionFilters().add(extFilter);
            String downloadsLocation = System.getProperty("user.home") + "/Downloads/";
            /// get a File file with name as you save in file chooser

            System.out.println("Inside runlator" + Thread.currentThread());
            file = fc.showSaveDialog(downloadMultiTraceFileButton.getScene().getWindow());
            System.out.println("Path of file->" + file.getAbsolutePath());
            file.mkdirs();

            File sourceFile = file;
            String s = file.getAbsolutePath().substring(0, file.getAbsolutePath().length() - 4);
            destFile = new File(s);
            if (sourceFile.renameTo(destFile)) {
                System.out.println("File renamed successfully" + Thread.currentThread());
            } else {
                System.out.println("Failed to rename file");
            }

            /// object of the folder made after renaming
            File theDir = new File(destFile.getAbsolutePath());

            /// Iterating through the slected item of trace files one by one for create a file and then fill it
            Task task = new Task() {
                @Override
                protected Void call() throws Exception {
                    for (String str : traceFileListView.getSelectionModel().getSelectedItems()) {
                        System.out.println("inside of the for loop " + Thread.currentThread());
                        File myFile = new File(theDir.getAbsolutePath() + "\\" + str);
                        /// make new file if myFile doesnt exist
                        if (!myFile.exists()) {
                            myFile.createNewFile();
                        }
                        /// get writer object of FileWriter for writing finally on file from 
                        System.out.println("t1--------- " + Thread.currentThread());

                        FileWriter writer = new FileWriter(myFile);
                        System.out.println("t2----------- " + Thread.currentThread());
                        /// query for getting content of trace file
                        String querySubPart = "select payload from gv$diag_trace_file_contents where trace_filename='" + str + "'";
                        System.out.println("t3----------- " + Thread.currentThread());
                        PreparedStatement preparedStatement = conn.prepareStatement(querySubPart);
                        System.out.println("t4----------- " + Thread.currentThread());
                        ResultSet resultSet = preparedStatement.executeQuery();
                        resultSet.setFetchSize(200);
                        ///  writing into file from resultSet
                        System.out.println("writing before while loop " + Thread.currentThread());
                        while (resultSet.next()) {
                            try {
                                writer.write(resultSet.getString(1));
                                System.out.println(resultSet.getString(1));
                            } catch (IOException ex) {
                                System.out.println("inside try of write");
                            }
                        }
                        writer.close();
                    }
                    wait1.notifyFunction();
                    updateProgress(1, 1);
                    return null;

                }

            };

            System.err.println("Thread created" + Thread.currentThread());
            new Thread(task).start();
            wait1.waitFunction();

            task.setOnSucceeded((e) -> {
                System.out.println("Inside set on succeeded"+Thread.currentThread());
                //// closing the window open where download button was there
                ((Stage) generateTraceFileButton.getScene().getWindow()).close();
                /// getting sourcepath folder which is to be zipped
                System.out.println("before zip");
                String sourcePath = theDir.getAbsolutePath();
                // name of zip folder
                String destinationpath = theDir + ".zip";
                System.out.println(destinationpath);

                try {
                    packInZipCode(sourcePath, destinationpath);
                } catch (IOException ex) {
                    Logger.getLogger(GenerateTraceFilesController.class.getName()).log(Level.SEVERE, null, ex);
                }

          
            });
  

        } catch (SQLException e) {

            System.out.println("Exception caught \n" + e.toString());
            if (e.toString().trim().toUpperCase().contains("ORA-48127")) {
                ....
            }
        }
    }
  • 2
    Maybe you want [showAndWait](https://stackoverflow.com/questions/14941084/javafx2-can-i-pause-a-background-task-service). Not sure, perhaps your situation is different and you want to wait without showing. I didn’t study your code, but I think the question could be improved by providing a [mcve] and a better explanation of what you are doing, what needs to wait, and why, until what condition and for how long. Task javadoc also provides info on feeding back partial results from a task that you can action in case that is what you want. – jewelsea May 13 '22 at 03:56
  • UI is event driven. Normally you will respond to events or notifications instead of waiting. For example making a Platform.runLater call from another thread or registering a change listener if on the same thread or listening to the task lifecycle status, messages, result property changes and events. – jewelsea May 13 '22 at 04:03
  • 1
    Why do you want to wait? Is it to do something after the background task completes? If so, then you should think in a more _event-driven_ fashion. Use `Task` and add listeners to its properties so that you can react to it completing. – Slaw May 13 '22 at 04:04
  • Yes Slaw, there is some work left after completing the background task. – abhishek kr sahu May 13 '22 at 04:19
  • Then you should be using something like `theTask.setOnSucceeded(e -> { /* the work to do after the task completes successfully */ });`. You don't have to do all the work inside the handler; it could call a method (even start another background task). Note that the handler will be invoked on the FX thread. – Slaw May 13 '22 at 04:23
  • Hi Slaw, A small doubt. Inside `task.setOnSucceeded(e ->{});` what is "e" in my code. Please refer to my code above, and please tell me. I tried this by just leaving e there then it is not working. – abhishek kr sahu May 13 '22 at 05:07
  • When I tried your approach then, `java.sql.SQLException: Connection is closed` error is coming at `PreparedStatement preparedStatement = conn.prepareStatement(querySubPart);` this line. Why is this, any solution? – abhishek kr sahu May 13 '22 at 05:19
  • You can see what `e` is by looking at the [method documentation](https://openjfx.io/javadoc/17/javafx.graphics/javafx/concurrent/Task.html#setOnSucceeded(javafx.event.EventHandler)). It is part of a [lambda expression](https://jenkov.com/tutorials/java/lambda-expressions.html) and is of type [WorkerStateEvent](https://openjfx.io/javadoc/17/javafx.graphics/javafx/concurrent/WorkerStateEvent.html). You already know from the call that you are processing a `WORKER_STATE_SUCCEEDED` event so you can ignore the `e` value, just define it and forget it. – jewelsea May 13 '22 at 05:37
  • You should not be getting the database connection on one thread and using it on another. That issue is unrelated to your on succeeded processing which does not appear to use the db. Get the connection and handle sql statements and errors in the task call method only. When you have an error, like the sql error, then log and report the full stack trace, not just the message, then It is easier to track down the cause of issues. – jewelsea May 13 '22 at 05:55
  • 1
    I don’t know what `waitNotifyClass` is, but I advise you to completely get rid of it, I think it will only cause you issues and not solve your problems. You must never block the UI thread. – jewelsea May 13 '22 at 06:05
  • CPass the selected item values to the task (see the task javadoc on how to do that in a thread safe manner). You should not access properties set by the UI, like the selection model, off of the JavaFX thread. – jewelsea May 13 '22 at 06:15
  • But My above code was working fine. After implementing setOnSucceded it is not working. – abhishek kr sahu May 13 '22 at 07:10
  • 1
    I don't see how moving what code you had outside `setOnSucceeded` into `setOnSucceeded` can be leading to a connection-closed error. As an aside, why do you want to ZIP the file on the FX thread instead of on the background thread (you could do it in the same task)? – Slaw May 13 '22 at 07:54
  • zipping is something that a background thread can't do. So ,I am doing this in Main Thread. Btw, My problem is solved. Thank You Very Much guys. – abhishek kr sahu May 13 '22 at 09:51
  • 1
    A background thread can zip a file. It is only operations which interact with the active scene graph and related properties that must be run on the JavaFX thread. You don’t show ode for `packInZipCode`, from the name it would not appear to have anything to do with the scene graph. But I guess if you put some scene graph related operations in there, then those would need to run in the JavaFX thread. – jewelsea May 13 '22 at 16:31
  • 1
    “But My above code was working fine.” -> with multithreaded code, it can appear to work but still be vulnerable to unpredictable [race conditions](https://en.m.wikipedia.org/wiki/Race_condition) if you access or modify shared resources and variables from different threads without using immutability or mutual exclusion blocks. Just adding debug statements or refactoring and moving code that is vulnerable can sometimes cause the existing race condition to trigger more often – jewelsea May 13 '22 at 16:36

1 Answers1

0

If you want your main thread to wait, and at the same time you don't want your UI to be frozen, then you can use:

task.setOnSucceeded(e->{'your code'})

For example:

Thread t1 = new Thread(task);
t1.start();
Thread.interrupted();

task.setOnSucceeded(e -> {
    System.out.println("inside succeeded");
    String sourcePath = theDir.getAbsolutePath();
    // name of zip folder
    String destinationpath = theDir + ".zip";
    System.out.println(destinationpath);

    try {
        //// calling the function where zipping code is written
        packInZipCode(sourcePath, destinationpath);
    } catch (IOException ex) {
        Logger.getLogger(GenerateTraceFilesController.class.getName()).log(Level.SEVERE, null, ex);
    }
    System.out.println("zip sucessfully ---" + Thread.currentThread());
    // for deleting the folder from which our zip folder made, bcs it is duplicate now, so dlete it
    deleteDirectoryLegacyIO(theDir);
    System.out.println("end succeeded");
});
            

This part of the code will be run by the JavaFX thread. and your UI will also not freeze. This solved my problem.

Why do you want to run those codes by the Javafx thread?

because some works /operations can be done only by the JavaFX thread. These works include Filechooser, file operations, label setting, etc. So you can't run these in your background thread, so we want to run them by the Main thread.

jewelsea
  • 150,031
  • 14
  • 366
  • 406
  • 1
    Small note, the main (JavaFX) thread isn't actually waiting here, it is running concurrently and able to process other UI, event, and rendering operations at the same time as the background task (which is why, with this solution, the UI is not frozen while the background task executes). The on succeeded handler is a completion handler for an asynchronous operation. It allows the JavaFX thread to process the completion event when it occurs and the JavaFX thread is free to do so. – jewelsea May 13 '22 at 18:43
  • Choosing a file via the UI should be done on the FX thread, true. But file operations (outside simple path calculations that do no I/O), and really all I/O operations, are exactly the sort of thing that _should not_ be done on the FX thread. If you want to show the user progress and messages, then you can make use of the `Task#updateMessage(String)` and `Task#updateProgress(...)` methods and have the UI (e.g. a label and progress bar) bind to the `progress` and `message` properties of the `Task`. Those "update" methods coalesce the updates on the FX thread. – Slaw May 14 '22 at 00:40
  • It may help to read [Concurrency in JavaFX](https://docs.oracle.com/javase/8/javafx/interoperability-tutorial/concurrency.htm#JFXIP546). – Slaw May 14 '22 at 00:45