JavaFX:文本区域中的控制台日志+;多线程和任务

JavaFX:文本区域中的控制台日志+;多线程和任务,java,multithreading,javafx,console,task,Java,Multithreading,Javafx,Console,Task,我正试图实现一个许多人在StackOverflow上尝试过的盛宴:在基于JavaFX构建的TextArea中显示Java应用程序的控制台输出。 我已经设法在上述文本区域中显示了我的输出,但和其他许多人一样,我的UI冻结了,因为这个线程正在大量加载用于显示UI本身的线程 因此,我开始阅读关于Platform.runLater(),但它并没有解决我的问题,主要是因为我输出了大量文本,这会减慢所述函数的速度。环顾四周,我进入了一个新的领域,提出了一个基于Task的解决方案。然而,一旦我开始在文本区域显

我正试图实现一个许多人在StackOverflow上尝试过的盛宴:在基于JavaFX构建的TextArea中显示Java应用程序的控制台输出。
我已经设法在上述文本区域中显示了我的输出,但和其他许多人一样,我的UI冻结了,因为这个线程正在大量加载用于显示UI本身的线程

因此,我开始阅读关于
Platform.runLater()
,但它并没有解决我的问题,主要是因为我输出了大量文本,这会减慢所述函数的速度。环顾四周,我进入了一个新的领域,提出了一个基于
Task
的解决方案。然而,一旦我开始在文本区域显示我的控制台日志,我的用户界面就会冻结。我将向您展示我的一段代码,这样您就可以告诉我我遗漏了什么和/或做错了什么

这是我的JavaFX控制器的一个片段:

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

    }

}
公共类MainViewController扩展AbstractController实现可初始化{
@FXML私有文本区域文本区域日志;
@凌驾
公共void初始化(URL位置、ResourceBundle资源){
任务=新任务(){
@凌驾
受保护的Void调用()引发异常{
布尔值fxApplicationThread=Platform.isFxApplicationThread();
System.out.println(“在FXApplicationRead上调用:+FXApplicationRead”);
控制台=新控制台(文本区域日志);
PrintStream ps=新的PrintStream(控制台,真);
系统放样(ps);
系统设置错误(ps);
返回null;
}
@凌驾
受保护的无效已成功(){
布尔值fxApplicationThread=Platform.isFxApplicationThread();
System.out.println(“在FXApplicationRead上调用:+FXApplicationRead”);
super.successed();
textAreaLog.textProperty().unbind();
}
};
textAreaLog.textProperty().bind(task.messageProperty());
新线程(任务).start();
}
//控制台类
公共静态类控制台扩展OutputStream{
专用区域输出;
控制台(文本区域ta){
这个输出=ta;
}
@凌驾
公共无效写入(int i)引发IOException{
output.appendText(String.valueOf((char)i));
}
}
}
我已经编辑了我之前链接的问题的答案中的代码,留下了所有调试消息只是为了帮助我


就这些。我的UI只是冻结,即使我显然是在后台运行重载任务,而不是直接在UI线程中运行

  • System.out.println(“text”)
    是一种同步方法
  • 在ui线程外部访问ui组件
当您从ui线程调用
System.out.println(“text”)
时,System.out上的同步将导致ui在同步期间冻结。 您可以检查这是否是如下所示的原因;(您必须像下面这样包装所有System.out调用,以便仅测试上述理论是否正确)
这将导致
println
方法在不同线程中同步。(公共池线程)

您还应该更新ui线程中的输出组件。(在这种情况下,问题就解决了)


您向控制台/
stdout
写入内容的部分在哪里?既然
任务
几乎立即返回,它有什么用途?而且,这不就是抛出一个异常吗?您可以绑定文本区域的
textProperty()
,然后在仍然绑定的情况下,通过控制台和
System.out.println(…)
succeed()
方法中调用
appendText()
。我的stdout被写在代码的四周,主要显示我从控制单元收到的电报。这是一个连续的任务,主要是因为控制单元不断向我发送新的信息。就我所见,我没有得到任何例外,除了我的UI冻结的事实。如果有一个合适的方法来实现我的目标,请告诉我!我不是Java方面的专业人士,我刚刚开始使用JavaFX。作为重定向系统输出的替代方法,我建议看看。如果底层需求必须是JavaFX中的系统输出重定向,那么也请参阅:@jewelsea,谢谢。我也会看看这些话题。现在我的代码正在运行,但是找到一个更好的方法重定向我的输出会很好。谢谢!我明天会告诉你的。关于“如果您没有使用Java8”的评论呢?目前我使用的是Java 9。我使用lambda将runnable传递给
平台。runLater()
,如果您低于Java 8,则必须像
新建runnable(…)
那样执行。好的,现在我明白了。为什么您认为在这行代码中使用runLater()是合适的?我已经读到,当你调用太多该方法的实例时,程序可能会开始变慢,最后冻结。在我的代码中,我正在输出一个连续的stdout流,我担心情况会是这样。我会让你知道的。谢谢你的帮助。@Davide3i据我所知,目前还没有线程安全的UI框架。他们要求仅从UI线程访问UI组件,因为他们无法管理从其他线程更改的UI组件的状态。这是UI框架的规则之一。如果您检查Swing的HelloWorld示例,它们都是从SwingUtilits.invokeLater()开始的。我认为他们在这方面做得不一样Fx@miroh,谢谢,罪犯没有使用
Platform.runLater(()->output.appendText(String.valueOf((char)i))
CompletableFuture.runAsync(()->System.out.println("text"));
    // or create new runnable if you are not using java8
    Platform.runLater(()->output.appendText(String.valueOf((char) i)));