单击按钮后JavaFX收到无限循环错误

时间:2017-11-05 19:12:45

标签: java button javafx infinite-loop

我发布了一个与之前类似的问题,但我还不够具体。这是我的代码的简化版本:

import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.scene.control.ProgressBar;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.layout.HBox;
import javafx.scene.Scene;
import javafx.concurrent.Task;
   import javafx.stage.Stage;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.scene.control.Button;
import javafx.scene.control.MenuBar;
import javafx.scene.control.Menu;
 import javafx.scene.control.MenuItem;
import javafx.scene.control.Separator;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.text.Text;
 import javafx.scene.text.Font;
import javafx.geometry.Pos;
import java.net.URL;
import javafx.scene.image.Image;
import javafx.scene.layout.GridPane;
import javafx.scene.control.Slider;

public class ProgressTest extends Application {

boolean play = true;
int x = 0;
@Override
public void start(Stage stage) {
GridPane pane = new GridPane(); //pane and Hboxes
HBox hbox = new HBox();

Button update = new Button("Start");
update.setOnAction( e -> {
    while(play == true)
    {
        System.out.println(++x);
    }
});

Button pause = new Button("Pause");
pause.setOnAction( e -> {
    if(play == true)
    {
        pause.setText("Play");
        play = false;
    }

    else
    {
        pause.setText("Pause");
        play = true;
    }
});


hbox.getChildren().addAll(update, pause);
pane.add(hbox, 0, 1);
Scene scene = new Scene(pane);
stage.setMaxWidth(655);
stage.setMaxHeight(620);

stage.setTitle("Gallery!");
stage.setScene(scene);

stage.sizeToScene();

stage.show();

}


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

}

使用此代码的想法是,当用户点击"开始"按钮,程序应打印出x上升,并在用户点击"暂停"时暂停,并在用户点击"播放"时再次恢复。

问题是每当我点击" Play"按钮,程序进入无限循环,我无法按暂停按钮停止它。我这样做的方式有问题吗?是否有任何技巧让这个工作?任何帮助都将非常感激。

另外,我知道其中一些可能有语法错误,但我知道我的代码副本是正确的,我更多地考虑了如何使其工作的逻辑。 / p>

1 个答案:

答案 0 :(得分:1)

您的实施存在两个大问题:

  1. GUI在JavaFX线程上运行。为了保持响应,其中的任何操作必须快速完成。当您在此线程上运行像循环这样的长计算时,整个GUI都会被阻止。
  2. 设法将play设置为false后,循环将退出,点击简历将不执行任何操作。
  3. 有几种方法可以解决这个问题。我将使用ThreadCountDownLatch演示一个。

    我无法解释问题范围内的线程是什么(SO和各处的大量材料),但这里的相关内容是在不是JavaFX线程的线程上执行代价高昂的操作将解决第1点。

    CountDownLatch用于阻止线程(或多个)执行,直到释放/断开锁存器为止,线程将继续执行。它使用int进行初始化,表示在释放之前需要倒计时的次数。到达锁存器await方法的线程阻塞,直到锁存器的countDown方法被调用指定的次数。

    以下是示例代码:

    import java.util.concurrent.CountDownLatch;
    
    import javafx.application.Application;
    import javafx.beans.binding.Bindings;
    import javafx.beans.property.BooleanProperty;
    import javafx.beans.property.SimpleBooleanProperty;
    import javafx.scene.Scene;
    import javafx.scene.control.Button;
    import javafx.scene.layout.HBox;
    import javafx.stage.Stage;
    
    public class ProgressTest extends Application {
    
        volatile CountDownLatch cl = new CountDownLatch(1);
    
        @Override
        public void start(Stage stage) {
            Thread thread = new Thread(() -> {
                int x = 0;
                while (true) {
                    try {
                        cl.await();
                    } catch (InterruptedException e1) {
                        e1.printStackTrace();
                    }
                    System.out.println(++x);
                }
            });
            thread.setDaemon(true);
    
            Button update = new Button("Start");
            update.setOnAction(e -> {
                if (!thread.isAlive()) {
                    cl.countDown();
                    thread.start();
                }
            });
    
            BooleanProperty running = new SimpleBooleanProperty(true);
    
            Button pause = new Button("Pause");
            pause.textProperty().bind(Bindings.when(running).then("Pause").otherwise("Play"));
            pause.setOnAction(e -> {
                if (running.get()) {
                    cl = new CountDownLatch(1);
                }
                else {
                    cl.countDown();
                }
                running.set(!running.get());
            });
    
            HBox hbox = new HBox(update, pause);
            Scene scene = new Scene(hbox);
            stage.setScene(scene);
            stage.setTitle("Gallery!");
            stage.sizeToScene();
            stage.show();
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    

    线程尽可能快地吐出数字""使用while(true)循环,只能通过await方法暂停它。当您按下启动时,闩锁被破坏,线程开始并连续执行。当您按下Pause时,会在其位置创建一个新的锁存器(一个锁存器是一次性的,它无法重置),这会导致线程在await中等待,直到有人用它来打破它countDown方法(一次调用就足够了,因为我们用1实例化了它)。这就是Resume所做的。

    在线程上调用setDaemon(true)可确保它允许JVM退出而无需等待它。如果线程必须在JVM存在之前完成(例如,它不是后台线程),则可以将其删除。

    我做了一个锁存器volatile,它保证不同的线程会看到相同的值。另请参阅Do you ever use the volatile keyword in Java?和其他可用来源。在这个特定的情况下,你不需要点对点线程同步,所以它不会有明显的效果,但它应该在那里。

    请注意,我在Start上添加了一个小的检查,表明线程尚未运行,因为在线程运行时启动线程会引发异常。如果在执行期间按下“开始”,则您没有指定要执行的操作。

    虽然与您的问题无关,但我们已经演示了如何利用JavaFX binding API自动更新按钮的文字boolean的价值。我"晋升"控件boolean到属性并将按钮的文本绑定到其值。但这对于这种情况可能没那么有用。

    备注:

    • 您正在设置场景的高度和宽度,然后拨打sizeToScene,这会使之前的呼叫变得多余。
    • 您不需要使用==检查布尔值,您可以直接使用它:if (b == true)相当于if (b)if (b == false)相当于{ {1}}。
    • if (!b)的更好名称将代表一个州("正在运行" /"暂停")而不是一个动作("运行&# 34; /"暂停"。)