Sven pegels
Sven pegels

Reputation: 65

How to wait for the end of a long process (on another thread)?

I'm writing an application (for personal use) that allows me to send a string over usb to an Arduino.

I wrote this method for sending the data:

    /**
     * Sends the data to the Arduino.
     * A new Thread is created for sending the data.
     * A transmission cool-down is started before send() method can be used again.
     * @param data the data to send to the Arduino
     */
    public void send(String data) {
        if (connected && !sending) {
            // Set 'sending' to true so only 1 Thread can be active at a time
            sending = true;
            // Create a new thread for sending the data
            Thread thread = new Thread(() -> {
                // Send the data
                PrintWriter output = new PrintWriter(chosenPort.getOutputStream());
                output.print(data);
                System.out.println("Data sended");
                output.flush();
                // Wait for the transmission cool-down and set 'sending' to false to allow for another Thread to send data
                try { Thread.sleep(transmissionCoolDown); } catch (InterruptedException interruptedException) { interruptedException.printStackTrace(); }
                sending = false;
                System.out.println("cooldown is over");
            });
            thread.start();
        }
    }

sending is a boolean value that I use to indicate whether a Thread is sending data. The transmissionCooldown is simply to enforce a certain waiting period before data can be sent again.

And this is where the method is being used:

    @FXML
    private void sendClicked() {
        // Only do something if a connection is active and an image is selected.
        // Should be unnecessary since the send button is only enables when both are true.
        if (connected && selectedIV.getImage() != null) {
            if (!sending) {
                // Save the original text of the send button and disable the disconnect button
                String ogText = sendButton.getText();
                System.out.println(ogText);
                connectButton.setDisable(true);
                // If the data has not been saved before, get the data by formatting the image
                if (data == null) {
                    data = imgCon.toStringFormat(true);
                }
                ardComm.send(data);
                // While the ArduinoCommunicator is busy sending, change the text on the send button to indicate the data is being transmitted
                sendButton.setText("busy");
                while (ardComm.isSending()) {

                }
                // Restore the text on the send button
                sendButton.setText(ogText);
                connectButton.setDisable(false);
            }
        }
    }

sendButton is the JavaFX Button that calls the sendClicked() method and ardCom is and instance of the class that contains the send() method. isSending() simply returns the sending attribute of the ardCom, which is set to true at the start of the send() method and set to false when the Thread is done sending.

The problem is with this piece of code:

    sendButton.setText("busy");
                while (ardComm.isSending()) {

                }
                // Restore the text on the send button
                sendButton.setText(ogText);

I'm trying to set the sendButton's text to busy to indicate that the data is being sent, then loop until the data transmission is finished (sending is set to false) and end it with changing the text on the sendButton back to the original text. I know this is probably not the best way to achieve this, but I was playing around and could not figure out why this is not working as expected.

The problem is that for some reason, the while loop never ends.

Upvotes: 5

Views: 162

Answers (2)

c0der
c0der

Reputation: 18792

Think of JavaFx Application as an application that runs on a single thread.
When this thread is busy with running the long while loop (while (ardComm.isSending()), it does not update the gui. The gui becomes unresponsive (freezes).
For more information see Implementing long-running tasks on the JavaFX Application thread inevitably makes an application UI unresponsive.

Consider listening to change events rather than waiting on the JavaFx application thread:

import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;

public class Main extends Application {

    private Button sendButton;

    @Override
    public void start(Stage primaryStage) throws Exception{

        ArdCom ardComm = new ArdCom();
        sendButton = new Button("Send");
        sendButton.setOnAction(event-> ardComm.send());
        ardComm.isSending().addListener((observable, oldValue, newValue) -> {
            if(newValue){
                Platform.runLater (() ->sendButton.setText("Busy"));
            }else{
                Platform.runLater (() ->sendButton.setText("Send"));
            }
        });
        AnchorPane root = new AnchorPane(sendButton);
        primaryStage.setScene(new Scene(root));
        primaryStage.show();
    }

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

class ArdCom {

    private final ReadOnlyBooleanWrapper sending = new ReadOnlyBooleanWrapper(false);

    void send() {//synchronize if accessed from multi thread

        if (! sending.get() ) {
            sending.set(true);
            Thread thread = new Thread(() -> {
                System.out.println("Send started");
                try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); }
                sending.set(false);
                System.out.println("Send completed");
            });
            thread.start();
        }
    }

    SimpleBooleanProperty isSending(){
        return sending;
    }
}

Upvotes: 3

Sven pegels
Sven pegels

Reputation: 65

Thanks to @NomadMaker I found that making the sending variable volatile explains the problem. As far as I understand it, it seems to me that the JavaFX Thread is too busy to update the value of sending causing it to loop infinitely. Making it volatile makes sending stored in main memory instead of being cached thread-locally.

The weird thing is that when I change the loop to actually do something every iteration, like this:

    int n = 0;
    while (ardComm.isSending()) {
        System.out.println(n);
        n++;
    }

it does not loop infinitely. (Maybe it's because println() and n++ are less memory heavy, leaving enough time or something for the sending variable to update. But don't quote me on that)

Upvotes: 0

Related Questions