使用多线程时GUI仍然冻结

时间:2018-03-25 03:18:56

标签: java performance user-interface javafx

我目前是一名计算机科学专业的学生,​​正在使用GUI开展我的第一个项目。我遇到了程序滞后的问题。我们的教授指导我们为此目的使用Threads。虽然我们没有在课堂上做过任何例子,但他告诉过我们。我正在使用一个线程从iTunes Search API下载信息。

问题

当我第一次打开程序时,应该显示一个进度条(它确实如此),并且应该更新下载的进度。虽然,我还没有找到那个部分,我试图先用Timer创建一个模拟进度条。但是,当我这样做时,进度条仅更新为2.0%并被卡住。我相信这是因为generateURLS方法阻止了Timer。但我想,计时器会在自己的Thread上运行,从而导致它不会阻塞。我真的很想澄清一下这个!

public void start(Stage stage) {
    StackPane root = new StackPane();
    VBox mainContent = new VBox();
    Scene scene = new Scene(root);  

    ProgressBar loadingOverlay = new ProgressBar();

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

    Runnable r = () -> {
        String[] URLS = Controls.generateURLS("rock", 50);

        Platform.runLater(() -> {
            images.fill(URLS);

            root.getChildren().remove(loadingOverlay);
            stage.sizeToScene();
        });
    };
    Thread t = new Thread(r);
    t.setDaemon(true);

    new Timer().scheduleAtFixedRate(new TimerTask(){
        double percent = 0;

        @Override
        public void run(){
                percent += 0.01;
                loadingOverlay.setProgress(percent);
        }
    }, 0, 1000);

    t.start();
    stage.show();
} 

使用Gson库生成URLS的代码:

    static JsonElement parseQuery (String query, int numberOf) throws IOException {
        query = "https://itunes.apple.com/search?limit="+numberOf+"&term=" + URLEncoder.encode(query);

        URL url = new URL(query);

        InputStreamReader reader = new InputStreamReader(url.openStream());
        JsonParser jp = new JsonParser();

        return jp.parse(reader);
    }

    static String[] generateURLS (String query, int numberOf) {
        String[] URLS = new String[numberOf];
        try {
            JsonElement json = parseQuery(query, numberOf);

            JsonObject root = json.getAsJsonObject();                      // root of response
            JsonArray results = root.getAsJsonArray("results");          // "results" array
            int numResults = results.size();                             // "results" array size
            for (int i = 0; i < numResults; i++) {                       
                JsonObject result = results.get(i).getAsJsonObject();    // object i in array
                JsonElement artworkUrl100 = result.get("artworkUrl100"); // artworkUrl100 member
                if (artworkUrl100 != null) {                             // member might not exist
                     String artUrl = artworkUrl100.getAsString();        // get member as string
                     URLS[i] = artUrl;                       // print the string
                } // if
            } // for
        } catch (IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
        return URLS;
    }

1 个答案:

答案 0 :(得分:0)

由于我没有代码的可运行示例,我不能说你为什么会遇到滞后问题。

更新----

因此,删除sizeToScene后调用ProgressBar可能会“重新显示”。这可以解释为什么进度停止在2.0% - 这是因为进度条不再在屏幕上活动,但由于某种原因,UI尚未刷新。

----

我可以帮助您的一件事是,您可以如何正确更新进度条。

理解,大多数GUI框架都不是线程安全的,这意味着您应该尝试从主事件线程之外更新它们。以这种方式使用java.util.Timer不仅不能让您准确了解已完成的工作量,而且还没有以线程安全的方式执行。

相反,我为您的示例代码添加了一个实现了一个简单的可观察属性,该属性可用于监视对进度状态的更改,并允许感兴趣的各方(即观察者)相应地执行某些任务。

import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLEncoder;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class Test extends Application {

    @Override
    public void start(Stage stage) {
        StackPane root = new StackPane();
        VBox mainContent = new VBox();
        Scene scene = new Scene(root);

        ProgressBar loadingOverlay = new ProgressBar();

        root.getChildren().add(loadingOverlay);

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

        Query query = new Query();
        query.progressProperty().addListener((ObservableValue<? extends Number> observable, Number oldValue, Number newValue) -> {
            Platform.runLater(() -> {
                loadingOverlay.setProgress(newValue.doubleValue());
            });
        });

        Runnable r = () -> {
            try {
                query.parseQuery("rock", 50);
            } catch (IOException ex) {
                ex.printStackTrace();
            }

            Platform.runLater(() -> {
                System.out.println("Done");
                root.getChildren().remove(loadingOverlay);
                root.getChildren().add(new Button("All done here"));
                stage.sizeToScene();
            });
        };
        new Thread(r).start();

        stage.show();
    }

    public class Query {

        private ReadOnlyDoubleWrapper progressProperty;

        public Query() {
            progressProperty = new ReadOnlyDoubleWrapper(this, "progress");
        }

        public ReadOnlyDoubleProperty progressProperty() {
            return progressProperty;
        }

        void parseQuery(String query, int numberOf) throws IOException {
            query = "https://itunes.apple.com/search?limit=" + numberOf + "&term=" + URLEncoder.encode(query);

            URL url = new URL(query);

            progressProperty.set(0.0);

            try (InputStreamReader reader = new InputStreamReader(url.openStream())) {
                for (int index = 0; index <= numberOf; index++) {
                    double progress = index / (double) numberOf;
                    progressProperty.set(progress);
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException ex) {
                    }
                }
            }
//      JsonParser jp = new JsonParser();

//      return jp.parse(reader);
        }

//  static String[] generateURLS(String query,^ int numberOf) {
//      String[] URLS = new String[numberOf];
//      try {
//          JsonElement json = parseQuery(query, numberOf);
//
//          JsonObject root = json.getAsJsonObject();                      // root of response
//          JsonArray results = root.getAsJsonArray("results");          // "results" array
//          int numResults = results.size();                             // "results" array size
//          for (int i = 0; i < numResults; i++) {
//              JsonObject result = results.get(i).getAsJsonObject();    // object i in array
//              JsonElement artworkUrl100 = result.get("artworkUrl100"); // artworkUrl100 member
//              if (artworkUrl100 != null) {                             // member might not exist
//                  String artUrl = artworkUrl100.getAsString();        // get member as string
//                  URLS[i] = artUrl;                       // print the string
//              } // if
//          } // for
//      } catch (IOException e1) {
//          // TODO Auto-generated catch block
//          e1.printStackTrace();
//      }
//      return URLS;
//  }
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }

}

我的“直觉”感觉是,您的下载代码正在完成并且进度条已被删除,但由于我目前不理解的原因,UI尚未更新。当我添加按钮时,进度条被正确删除。

并且......使用JavaFX Task ...

import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLEncoder;
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class Test extends Application {

    @Override
    public void start(Stage stage) {
        StackPane root = new StackPane();
        Scene scene = new Scene(root);

        ProgressBar loadingOverlay = new ProgressBar();

        root.getChildren().add(loadingOverlay);

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

        Query query = new Query("", 50);
        loadingOverlay.progressProperty().bind(query.progressProperty());
        query.setOnSucceeded((event) -> {
                System.out.println("Done");
                root.getChildren().remove(loadingOverlay);
                root.getChildren().add(new Button("All done here"));
                stage.sizeToScene();
        });
        new Thread(query).start();

        stage.show();
    }

    public class Query extends Task<URL[]> {

        private String query;
        private int count;

        public Query(String query, int count) {
            this.query = query;
            this.count = count;
        }

        @Override
        protected URL[] call() throws Exception {
            query = "https://itunes.apple.com/search?limit=" + count + "&term=" + URLEncoder.encode(query);

            URL url = new URL(query);

            try (InputStreamReader reader = new InputStreamReader(url.openStream())) {
                for (int index = 0; index <= count; index++) {
                    updateProgress(index, count);
                    try {
                        Thread.sleep(250);
                    } catch (InterruptedException ex) {
                    }
                }
            }

            return new URL[0];
        }
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }

}