【问题标题】:How to wait for the end of a long process (on another thread)?如何等待一个长进程的结束(在另一个线程上)?
【发布时间】:2020-09-21 15:06:58
【问题描述】:

我正在编写一个应用程序(供个人使用),它允许我通过 USB 将字符串发送到 Arduino。

我写了这个发送数据的方法:

    /**
     * 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 是一个布尔值,我用它来指示线程是否正在发送数据。 transmissionCooldown 只是在数据可以再次发送之前强制执行一定的等待时间。

这是使用该方法的地方:

    @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 是调用sendClicked() 方法的JavaFX 按钮,ardCom 是包含send() 方法的类的实例。 isSending() 只是返回ardCom 的发送属性,在send() 方法开始时设置为true,在线程完成发送时设置为false。

问题出在这段代码上:

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

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

我正在尝试将sendButton 的文本设置为忙以指示正在发送数据,然后循环直到数据传输完成(发送设置为 false)并以更改文本结束sendButton 回到原文。我知道这可能不是实现这一目标的最佳方法,但我一直在玩,无法弄清楚为什么它没有按预期工作。

问题是由于某种原因,while 循环永远不会结束。

【问题讨论】:

  • 将 JavaFx 应用程序视为在单线程上运行的应用程序。当这个线程忙于运行长时间循环while (ardComm.isSending()) 时,它不会更新 gui。 gui 变得无响应(冻结)。
  • 发送不稳定吗?

标签: java multithreading javafx


【解决方案1】:

将 JavaFx 应用程序视为在单个线程上运行的应用程序。
当此线程忙于运行长 while 循环 (while (ardComm.isSending()) 时,它不会更新 gui。 gui 变得无响应(冻结)。
如需更多信息,请参阅Implementing long-running tasks on the JavaFX Application thread inevitably makes an application UI unresponsive

考虑监听更改事件而不是等待 JavaFx 应用程序线程:

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;
    }
}

【讨论】:

  • 非常感谢,这看起来是我想要实现的更好的解决方案。
【解决方案2】:

感谢@NomadMaker,我发现将sending 变量设置为 volatile 可以解释问题。据我了解,在我看来,JavaFX 线程太忙了,无法更新 sending 的值,导致它无限循环。使其 volatile 使 sending 存储在主内存中,而不是在线程本地缓存。

奇怪的是,当我将循环更改为实际上每次迭代都做某事时,就像这样:

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

它不会无限循环。 (也许是因为println()n++ 内存较少,留出足够的时间或其他东西让sending 变量更新。但不要引用我的话)

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-07-10
  • 1970-01-01
  • 1970-01-01
  • 2014-05-31
  • 1970-01-01
  • 2017-09-28
  • 1970-01-01
相关资源
最近更新 更多