1

I'm trying to achieve a feast which many attempted here on StackOverflow: showing the console output of a Java application in a TextArea built on JavaFX.
I've managed to show my output in said TextArea but, as many others, my UI freezes, 'cause this thread is heavily loading the one used to show the UI itself.

So I've started reading about Platform.runLater(), but it doesn't solve my issue, mostly because I'm outputting a lot of text and this slows down said function. Looking around, I've got into this question, where a nice solution based on Task is proposed. Neverthless, my UI keeps freezing as soon as I start to show my console log into the TextArea. I'll show you a snippet of my code, so that you may be able to tell me what I'm missing and/or doing wrong.

This is a snippet of my JavaFX controller:

public class MainViewController extends AbstractController implements Initializable {

    @FXML private TextArea textAreaLog;

    @Override
    public void initialize(URL location, ResourceBundle resources) {

        Task<Void> task = new Task<Void>() {
            @Override
            protected Void call() throws Exception {
                boolean fxApplicationThread = Platform.isFxApplicationThread();
                System.out.println("Is call on FXApplicationThread: " + fxApplicationThread);

                Console console = new Console(textAreaLog);
                PrintStream ps = new PrintStream(console, true);
                System.setOut(ps);
                System.setErr(ps);

                return null;
            }

            @Override
            protected void succeeded() {
                boolean fxApplicationThread = Platform.isFxApplicationThread();
                System.out.println("Is call on FXApplicationThread: " + fxApplicationThread);
                super.succeeded();
                textAreaLog.textProperty().unbind();
            }
        };

        textAreaLog.textProperty().bind(task.messageProperty());
        new Thread(task).start();

    }

    // Console Class
    public static class Console extends OutputStream {

        private TextArea output;

        Console(TextArea ta) {
            this.output = ta;
        }

        @Override
        public void write(int i) throws IOException {
            output.appendText(String.valueOf((char) i));
        }

    }

}

I've edited the code taken from answer to the question I've previously linked, leaving all the debug messages just to help me out.

That's all. My UI just freezes, even if I'm apparently running my heavy-load task in the background instead of doing that directly in my UI thread.

Community
  • 1
  • 1
Davide3i
  • 1,035
  • 3
  • 15
  • 35
  • 1
    Where is the part you write something to the console/`stdout`? Since the `Task` returns pretty much immediately, what purpose does it serve? – Itai Feb 20 '18 at 15:58
  • Also, doesn't this just throw an exception? You bind the text area's `textProperty()` and then, while it is still bound, in the `succeeded()` method you call `appendText()` on it via the console and `System.out.println(...)`. – James_D Feb 20 '18 at 16:00
  • My stdout is been written all around the code, mostly showing the telegrams I am getting from a control unit. It's a continuos task, mostly because said control unit keeps sending me new infos. For what I've seen, I'm not getting any exception, except the fact my UI freezes. If there is a proper way to achieve what I'm trying to do, feel free to tell me! I'm not a pro in Java and I've just got into using JavaFX. – Davide3i Feb 20 '18 at 16:02
  • 1
    As an alternative to redirecting system output, I'd advise looking at [Most efficient way to log messages to JavaFX TextArea via threads with simple custom logging frameworks](https://stackoverflow.com/questions/24116858/most-efficient-way-to-log-messages-to-javafx-textarea-via-threads-with-simple-cu). If the underlying requirement must be system output redirection in JavaFX, then also see: [Replicating console functionality with a ListView](https://stackoverflow.com/questions/48589410/replicating-console-functionality-with-a-listview/48589707#48589707) – jewelsea Feb 20 '18 at 22:12
  • @jewelsea, thank you. I'll take a look at those topics too. Now my code is working, but finding a better way to redirect my output would be nice. – Davide3i Feb 21 '18 at 06:51

1 Answers1

2

I think root of the problem is one of the below;

  • System.out.println("text") is being a synchronized method.
  • accesing ui component outside of Ui thread

When you call System.out.println("text") from ui thread, the synchronization on System.out will cause UI to freeze for duration of synchronization. You can check if this is the cause like below;(You have to wrap all your System.out calls like below, for only to test if the above theory is correct)
This will cause println methods to synchronize in different thread.(common-pool threads)

CompletableFuture.runAsync(()->System.out.println("text"));

You should also update output component in ui thread.(Problem is solved with this in this case)

    // or create new runnable if you are not using java8
    Platform.runLater(()->output.appendText(String.valueOf((char) i))); 
miskender
  • 7,460
  • 1
  • 19
  • 23
  • Thank you! I'll let you know tomorrow. What about the comment about "if you are not using Java 8"? At the moment I'm on Java 9. – Davide3i Feb 20 '18 at 19:09
  • 1
    I used lambda to pass runnable to `Platform.runLater()` , if you were below java 8 you have do it like `new Runnable(...)`. – miskender Feb 20 '18 at 19:11
  • Ok, now I get it. Why do you think is it proper to use runLater() on that line of code? I've read that when you call too much instances of that method the program can start to get laggy and, at last, freeze. In my code I'm outputting a continuous flow of stdout and I'm afraid it will be the case. I'll let you know. Thanks for your help. – Davide3i Feb 20 '18 at 19:14
  • 1
    @Davide3i As far as I know, there is no UI framework exists which is thread-safe. They are requiring UI components to be accessed from UI thread alone, because they can't manage the state of UI components changing from other threads. This is the rule one of UI frameworks. If you check even HelloWorld examples of Swing, they are started with SwingUtilits.invokeLater(). I dont think they can do differently in Fx – miskender Feb 20 '18 at 19:24
  • 1
    @miroh, thank you, the culprit was not using `Platform.runLater(()->output.appendText(String.valueOf((char) i)));`. – Davide3i Feb 21 '18 at 06:54