1

I have this task run in a thread. The problem is that it freezes the UI every time it is executed. The freeze is longer when the internet is slow. How can I prevent the UI from freezing even if it is still gathering data from the url?

Task<Void> task = new Task<Void>(){
        @Override
        public Void call() throws Exception {
            while (true) {
                Platform.runLater(new Runnable() {
                    @Override
                    public void run(){
                        String json = null;
                        try {
                            psname = null;
                            PumpSites n = table.getSelectionModel().getSelectedItem();
                            psname = n.getPs();
                            if(psname == "Cubacub")
                                json = readUrl(""); //read json from thingspeak.com webpage
                            else if(psname == "Canduman")
                                json = readUrl("");
                        } catch (InterruptedIOException iioe)
                        {
                            btn1.setTextFill(Color.RED);
                            btn2.setTextFill(Color.RED);
                            btn3.setTextFill(Color.RED);
                            btn4.setTextFill(Color.RED);
                            btn5.setTextFill(Color.RED);
                            btn1.setText("NULL");
                            btn2.setText("NULL");
                            btn3.setText("NULL");
                            btn4.setText("NULL");
                            btn5.setText("NULL");
                        }
                        catch (IOException ioe)
                        {
                            btn1.setTextFill(Color.RED);
                            btn2.setTextFill(Color.RED);
                            btn3.setTextFill(Color.RED);
                            btn4.setTextFill(Color.RED);
                            btn5.setTextFill(Color.RED);
                            btn1.setText("NULL");
                            btn2.setText("NULL");
                            btn3.setText("NULL");
                            btn4.setText("NULL");
                            btn5.setText("NULL");
                        }
                        catch (Exception e1) {
                            btn1.setTextFill(Color.RED);
                            btn2.setTextFill(Color.RED);
                            btn3.setTextFill(Color.RED);
                            btn4.setTextFill(Color.RED);
                            btn5.setTextFill(Color.RED);
                            btn1.setText("NULL");
                            btn2.setText("NULL");
                            btn3.setText("NULL");
                            btn4.setText("NULL");
                            btn5.setText("NULL");
                        } 

                        Gson gson = new Gson();        
                        Page page = gson.fromJson(json, Page.class);

                        for (Item item : page.feeds)
                        {
                            det2 = 1;
                            btn1.setText(item.field1);
                            btn2.setText(item.field2);
                            btn3.setText(item.field3);
                            btn4.setText(item.field4);
                            btn5.setText(item.field5);
                            f2 = Float.parseFloat(item.field2);
                            f3 = Float.parseFloat(item.field3);
                            //float f5 = Float.parseFloat(item.field5);
                            if (f2 <= 10.0)
                            {
                                btn1.setTextFill(Color.RED);
                                btn2.setTextFill(Color.RED);
                            }
                            else
                            {
                                btn1.setTextFill(Color.BLUE);
                                btn2.setTextFill(Color.BLUE);
                            }
                            if (f3 < 0.9 || f3 > 1.2)
                            {
                                btn3.setTextFill(Color.RED);
                            }
                            else
                            {
                                btn3.setTextFill(Color.BLUE);
                            }
                            /*if (f5 > 5.0)
                            {
                                btn5.setTextFill(Color.RED);
                            }
                            else
                            {
                                btn5.setTextFill(Color.BLUE);
                            }*/
                            btn4.setTextFill(Color.BLUE);
                        }   
                        if(det2 == 0)
                        {
                            btn1.setTextFill(Color.RED);
                            btn2.setTextFill(Color.RED);
                            btn3.setTextFill(Color.RED);
                            btn4.setTextFill(Color.RED);
                            btn5.setTextFill(Color.RED);
                            btn1.setText("NULL");
                            btn2.setText("NULL");
                            btn3.setText("NULL");
                            btn4.setText("NULL");
                            btn5.setText("NULL");
                        }
                        det2 = 0;

                    }
                });
                Thread.sleep(10000);
            }
        }
    };
    Thread th = new Thread(task);
    th.setDaemon(true);
    th.start();

The problem is that it freezes the UI every time it is executed. The freeze is longer when the internet is slow. How can I prevent the UI from freezing even if it is still gathering data from the url?

  • 1
    See [this](https://dzone.com/articles/async-await-in-java) article for some tips using asynchronous constructs. – Jeroen Heier Aug 28 '18 at 03:54
  • Check [this answer](https://stackoverflow.com/a/30250308/1759128) to understand concurrency and async task in JavaFX. – ItachiUchiha Aug 28 '18 at 05:34
  • If multiple `catch` blocks execute the same code, you could replace them with a multi-catch: `catch (InterruptedIOException | IOException ex) { ... }` However since the catch clause for `Exception` also executes the same code and `Exception` is a supertype of the other 2 exception types, in this case you should remove the first 2 catch clauses and only keep `catch (Exception e1) ...` – fabian Aug 28 '18 at 06:59

2 Answers2

1

The UI thread freezes because you are still doing the all the logic on the JavaFX application Thread(Platform.runLater ).

You should do something like this instead:

public Void call() throws Exception 
{
        while (true) 
        {
            try
            {   
                //get json 
            } catch(Exception e) 
            {   
                Platform.runLater(new Runnable()    
                {
                    @Override
                    public void run()
                    {
                        //set buttons color and text here
                    }   
                 }
             }
             //Rest of your logic here
         }
}

The idea is that everything that is going to modify a UI component from a separate Thread should be handled in the Platform.runLater

Falla Coulibaly
  • 709
  • 11
  • 21
  • Because there is an infinite loop, you are still going to clog the JavaFX application thread by calling `Platform#runLater`. – ItachiUchiha Aug 28 '18 at 05:32
  • Thanks! I did that, but now the UI doesn't update anymore because it doesn't go inside the catch statement. – TheChosenWan Aug 28 '18 at 05:37
0

If you use a background thread invoke Platform.runLater with a long-running Runnable as parameter, you've effectively achieved nothing. The Runnable is still run on the JavaFX application thread freezing your app.

Instead you should collect all the data on the background thread and process it to the point where you simply need to adjust some properties of the scene. Then you use Platform.runLater to do those updates.

But the good news is that there is a class designed for this scenario that could simplify your code a bit: ScheduledService.

Just make sure that you don't access the GUI in any way from the background thread (neither for reading nor for setting properties).

The following example simplified example should demonstrate the general approach. It calculates some multiples of the value chosen via Spinner on a background thread delaying 10 sec between each calculation:

@Override
public void start(Stage primaryStage) {
    Spinner<Integer> spinner = new Spinner(1, 100, 1);

    // ensure the value is available in a way that allows synchronisation
    final AtomicReference<Integer> input = new AtomicReference<>(spinner.getValue());
    spinner.valueProperty().addListener((o, oldValue, newValue) -> input.set(newValue));

    final int outputCount = 10;

    GridPane root = new GridPane();
    root.add(spinner, 0, 0, 2, 1);

    // create output grid
    Text[] output = new Text[outputCount];
    for (int i = 1; i <= output.length; i++) {
        Text text = new Text(Integer.toString(spinner.getValue() * i));
        output[i - 1] = text;
        root.addRow(i, new Text("Value multiplied by " + i + " = "), text);
    }
    root.setPrefWidth(300);

    ScheduledService<int[]> service = new ScheduledService<int[]>() {

        @Override
        protected Task<int[]> createTask() {
            return new Task<int[]>() {

                @Override
                protected int[] call() throws Exception {
                    // retrieve value and set it to null to denote a change
                    // that was already handled to avoid doing unnecessary
                    // work
                    Integer value = input.getAndSet(null);

                    int[] result = null;

                    if (value != null) {
                        int valueAsInt = value;
                        result = new int[outputCount];
                        for (int i = 0; i < outputCount; i++) {
                            result[i] = (i + 1) * valueAsInt;
                        }
                    }

                    // simpulate delay
                    Thread.sleep(2000);

                    return result;
                }

            };
        }

    };

    service.valueProperty().addListener((o, oldValue, newValue) -> {
        // update GUI
        if (newValue != null) {
            for (int i = 0; i < outputCount; i++) {
                output[i].setText(Integer.toString(newValue[i]));
            }
        }
    });

    service.setPeriod(Duration.seconds(10));

    // make sure service uses a daemon thread
    service.setExecutor(Executors.newSingleThreadScheduledExecutor((Runnable r) -> {
        Thread t = new Thread(r);
        t.setDaemon(true);
        return t;
    }));

    service.start();

    Scene scene = new Scene(root);
    primaryStage.setScene(scene);
    primaryStage.show();
}

I recommend looking through the javadoc of ScheduledService to get familiar with it's capabilities. It also allows for things like reacting to exceptions in the task and specifying a backoff strategy.

fabian
  • 67,623
  • 12
  • 74
  • 102