简体   繁体   中英

GUI Still Freezes when using Multiple Threads

I am currently a Computer Science student working on my first project with GUI. I am running into issues with the program being laggy. Our professor has guided us into using Threads for this purpose. Though we have not done any in class examples he has told us about them. I am using a thread to download information from the iTunes Search API.

Problem

When I first open up the program, a progress bar should show (which it does) and should be updating the progress of the download. Although, I do not have that part quite figured out yet, I was trying to create a simulation progress bar using a Timer first. However, when I do that, the progress bar only updates to 2.0% and gets stuck. I believe this is because the generateURLS method is blocking the Timer. I thought however, that the timer would run on its own Thread, thus causing it not to block. I would really love some clarification on this!

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

Code for generating the URLS using the Gson library:

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

Since I don't have a runnable example of your code, I can't say why you might be experiencing lag issues.

Update ----

So, it would "appear" that calling sizeToScene after removing the ProgressBar may not be repainting the UI. This may explain why the progress stops at 2.0% - it's because the progress bar is no longer actively on the screen, but, for some reason, the UI hasn't been refreshed.

----

One thing I can help you with, is the how you might update the progress bar properly.

Understand, most GUI frameworks are not thread safe, this means that you should ever attempt to update them from outside of their main event thread. Using a java.util.Timer this way is not only not providing you with an accurate idea of how much work has been done, it's also not been performed in a thread safe manner.

Instead, I guttered your example code an implemented a simple observable property, which can be used to monitor changes to the progress state and allow interested parties (ie the observers) a chance perform some task accordingly.

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

}

My "gut" feeling is, your down load code is completing and the progress bar is been removed, but for reasons I don't understand at this time, the UI is not been updated. When I add the button in, the progress bar is removed correctly.

And ... with 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);
    }

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM