繁体   English   中英

JavaFX应用程序线程减慢然后冻结

[英]JavaFX Application Thread slows and then freezes

我有一个多线程JavaFX应用程序。 我有一堆后台线程正在调用此方法来从应用程序线程更新主UI。

public void writeToActivityLog(String message){

    class ThreadedTask implements Runnable {

        private String message;

        public ThreadedTask(String message){
            this.message = message;
        }

        @Override
        public void run() {
            try{
                //delete older text when there are more than 200 lines and append new text

                String text = outputLog.getText();
                String[] lines = text.split("\r\n");
                String newText = "";
                for(int i = 0; i < lines.length; i++){
                    if(i >= 200 || lines.length < 200){
                        newText += lines[i];
                    }
                }
                outputLog.setText(newText);
                outputLog.appendText(message + "\r\n");

                //scroll to the bottom of the log
                outputLog.setScrollTop(Double.MIN_VALUE);

            } catch(NullPointerException e){
                e.printStackTrace();
            }
        }
    }

    ThreadedTask thread = new ThreadedTask(message);
    Platform.runLater(thread);

}

首先,这种方法很好。 但是进入程序几秒钟后,它开始变慢,几秒钟之后整个UI冻结并停止响应用户输入(但它不会触发Windows“此程序没有响应”对话框)。 但是,查看应用程序日志文件和我的IDE控制台,后台线程仍在执行,似乎做得很好。

可以排队的Platform.runLater()请求的数量有一些限制吗? 或者是否有某种方式我可能有一个内存泄漏,正在杀死主应用程序线程,但没有对后台线程做任何事情? 我对多线程编程还很陌生,所以我不知道在这里想些什么。

我也知道JavaFX有另一个名为Services的并发工具,但我找不到任何关于何时以及为什么我应该使用它们而不是Platform.runLater() 我编写了其他JavaFX应用程序,使用Platform.runLater()从未遇到任何问题。

编辑:

非常感谢下面的两个答案。 问题与JavaFX或线程本身无关,而只是简单的坏代码。 这是固定代码,为了完成起见:

public void writeToActivityLog(String message){
    //Create a new thread to update the UI so that it doesn't freeze due to concurrency issues
    class ThreadedTask implements Runnable {

        private String message;

        public ThreadedTask(String message){
            this.message = message;
        }

        @Override
        public void run() {
            outputLog.setText(message);

            //scroll to the bottom of the log
            outputLog.setScrollTop(Double.MIN_VALUE);
        }
    }

    String wholeMessage = new StringBuilder().append(outputLog.getText()).append(message).toString();
    //modify the text of the output log so that it is 200 lines or less
    StringBuilder newText = new StringBuilder();
    String[] lines = wholeMessage.split("\r\n");

    for (int i=Math.max(0, lines.length - 200); i<lines.length; i++) {
        newText.append(new StringBuilder().append(lines[i]).append("\r\n").toString());
    }

    ThreadedTask thread = new ThreadedTask(newText.toString());
    Platform.runLater(thread);
}

通过使用+=运算符在循环中连接字符串,您正在执行大量工作。 如果您的缓冲区有199行,则执行连续级联将第一行复制近10,000次。 (虽然我无法弄清楚i >= 200条件正在做什么。此外,你正在使用最多200行的文本缓冲区并将其拆分为单独的字符串。这涉及大量复制。

问题是你在事件调度线程上做了所有这些工作,它阻塞了用户界面的处理。 我怀疑正在发生的事情是这项工作需要花费大量时间,并且在执行此任务时UI似乎会冻结。

我建议在事件处理之外移动更新日志行的逻辑,并让附加消息的线程执行更新缓冲区文本的工作。 然后,在一个简单设置outputLog文本的任务上调用runLater()。 这可以避免在事件线程上发生过多的字符串处理。

如果您要进行大量的字符串连接,请使用StringBuilder追加连续的文本行,然后在完成所有操作后调用toString() 这可以避免冗余复制。

编辑

错过了这个。 原始代码在“\\ r \\ n”上拆分,但使用再次将这些行连接在一起

newText += lines[i];

恢复换行符。 随着行的添加,它们最终会附加到“相同”行,因此即使文本只有一行,文本也会越来越长。 N平方附加行为可能不会进入问题。 相反,输出日志(我假设它是某种文本节点)必须在更大的文本片段上运行其换行算法。 这可能是事情逐渐减缓的原因。

在任何情况下,修复附加问题,修复行旋转逻辑以及将处理移出事件循环都应该有所帮助。

一些建议。

首先,这只是一个普通的Java事情,通过像这样的循环中的连接来构建String是一个非常糟糕的主意。 在许多情况下,现代编译器会为您修复此问题,但是您的if子句会使其更难以实现,并且您不太可能自动获得此优化。 您应该进行以下修改:

// String newText = "" ;
StringBuilder newText = new StringBuilder();
// ...
// newText += lines[i] ;
newText.append(lines[i]);
// ...
// outputLog.setText(newText);
outputLog.setText(newText.toString());

第二点是JavaFX并发问题。 你不是通过在这里创建线程来保存任何东西,因为你只需创建线程,然后通过直接将它传递给Platform.runLater(...)来安排它在FX Application Thread上运行。 所以整个run()方法只是在FX Application Thread上执行。 (代码中没有后台线程!)

遵守两条规则:1。仅更新FX应用程序线程2上的“实时”节点。不要在该线程上执行任何长时间运行的任务

javafx.concurrency中的Task类是一个Runnable(实际上是一个Callable,它更灵活一点),它提供了一些有用的生命周期方法,保证在FX Application Thread上运行。 因此,您可以使用Task来计算新的日志文本,返回新的日志文本,然后只需使用setOnSucceeded(..)来更新UI。 这看起来像:

class UpdateLogTask extends Task<String> { // a Task<T> has a call() method returning a T
    private final String currentLog ;
    private final String message ;
    public UpdateLogTask(String currentLog, String message) {
      this.currentLog = currentLog ;
      this.message = message ;
    }
    @Override
    public String call() throws Exception {
      String[] lines = currentLog.split("\r\n");
      StringBuilder text = new StringBuilder();
      for (int i=0; i<lines.length; i++) {
        if (i>=200 || lines.length < 200) {
          text.append(lines[i]);
        }
      }
      text.append(message).append("\r\n");
      return text.toString() ;
    }
}
final Task<String> updateLogTask = new UpdateLogTask(outputLog.getText(), message);
updateLogTask.setOnSucceeded(new EventHandler<WorkerStateEvenet>() {
    @Override
    public void handle(WorkerStateEvent event) {
      outputLog.setText(updateLogTask.getValue());
    }
});
Thread t = new Thread(updateLogTask);
t.setDaemon(true); // will terminate if JavaFX runtime terminates
t.start();

最后,我想如果你想要最后200行文本,你的逻辑是错误的。 尝试

for (int i=Math.max(0, lines.length - 200); i<lines.length; i++) {
  text.append(lines[i]);
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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