当前位置: 首页 > 知识库问答 >
问题:

JavaFX使线程等待并保存gui更新

戚俊人
2023-03-14

所以我有这样的东西:

public class test {
    final ProgressBar bar = new ProgressBar(0.0);
    final int result;
    final worker work = new worker();

// GUI Scene change happens here

    new Thread(() -> {
        result = work.doSomething(this.bar);
    }).start();
}

public class worker{
    public int doSomething(ProgressBar bar){
        for(int i = 1; i <= 1000000; i++)
            Platform.runLater(() -> { bar.setProgress(i/100000.0); });
        return 51;
    }
}

一切都很好,直到我不得不等待其他事情完成,然后才能继续运行,所以我这样修改了它:

public class test {
    final ProgressBar bar = new ProgressBar(0.0);
    TextArea area = new TextArea();
    int result;
    final worker work = new worker();
    final worker work2 = new worker2();
    final CountDownLatch latch1 = new CountDownLatch(1);

// GUI Scene change happens here

    new Thread(() -> {
        result = work.doSomething(this.bar);
        latch1.CountDown();
    }).start();

    latch1.await();

    new Thread(() -> {
        work2.doSomething(result, area);
    }).start();
}

public class worker{
    public int doSomething(ProgressBar bar){
        for(int i = 1; i <= 1000000; i++)
            Platform.runLater(() -> { bar.setProgress(i/100000.0); });
        return 51;
    }
}

public class worker2{
    ArrayList<String> list = new ArrayList<>();

    public int doSomething(int index, TextArea area){
            Platform.runLater(() -> { area.append(list.get(index)); });
    }
}

后来我做了这样的事情:

    public class test {
    final ProgressBar bar = new ProgressBar(0.0);
    TextArea area = new TextArea();
    int result;
    final worker work = new worker();
    final worker2 work2 = new worker2();
    final worker3 work3 = new worker3();
    final CountDownLatch latch1 = new CountDownLatch(1);
    final CountDownLatch latch2 = new CountDownLatch(Map.keySet().size());

    // This already has values
    // it is not really a file array list in the map, but it is easier to show it this way
    Map<String, ArrayList<File>> mapTypes; 

// GUI Scene change happens here

    new Thread(() -> {
        result = work.doSomething(this.bar);
        latch1.CountDown();
    }).start();

    latch1.await();

    new Thread(() -> {
        work2.doSomething(result, area);
    }).start();

    // Even thought I don't use it here I need a for each on the keyset
    mapTypes.keySet().forEach((String s) -> {
        new Thread(() -> {
            // Here I actually load classes with a reflection
            work3.doSomething(mapTypes.get(s), area);
            latch2.CountDown();
        }).start();
    }
    latch2.await();
    System.out.println("Done");
}

public class worker{
    public int doSomething(ProgressBar bar){
        for(int i = 1; i <= 1000000; i++)
            Platform.runLater(() -> { bar.setProgress(i/100000.0); });
        return 51;
    }
}

public class worker2{
    ArrayList<String> list = new ArrayList<>();

    public int doSomething(int index, TextArea area){
            Platform.runLater(() -> { area.append(list.get(index)); });
    }
}

public class worker3{
    public int doSomething(Arraylist<File> files, TextArea area){
        for (File f : files)
            Platform.runLater(() -> { area.append(f.getName()); });
    }
}

现在,我的gui在切换场景时开始滞后 - 这意味着 - 整个thread1自行处理,然后gui加载所有内容。经过一些研究,我认为发生这种情况是因为主线程正在处理runLater“请求”,并且由于wait(),主线程必须等到第一个辅助线程来到CountDown()。

我的问题是,在第一个线程完成之前,我如何管理主线程不启动第二个后台线程?额外的问题:什么是比Plattform.runlater()更有效的更新我的GUI的方法

注意:
我也看过这个问题,但它没有完全解决我的问题,因为我不需要排队线程。我更需要知道如何让主线程等到子线程完成,然后才继续。但是,主线程不能完全处于非活动状态,而是管理传入的更新请求。

提前谢谢你

使用的技术:-NetBeans
-JavaFX(没有FXML-代码中设计的一切)
-CSS
-Java(显然)
-Windows 10 pro

共有1个答案

袁法
2023-03-14

代码的(主要)问题是您在 JavaFX 应用程序线程上调用 latch.await(),这是一种阻塞方法。由于 JavaFX 应用程序线程负责更新 UI,因此在 latch.await() 发布之前,这可以防止 UI 被重新绘制。

您的问题的基本前提是错误的:您永远不想让UI线程暂停,因为它总是会使UI无响应并阻止任何更新。相反,您应该考虑在后台“执行一个工作单元”,可能会随着UI的进行进行更新,然后做一些事情来响应后台工作的完成。

代码的另一个潜在问题是,您通过< code>Platform.runLater()向FX应用程序线程提交了大量的< code>Runnable。您可能需要限制这些线程,使它们不会“淹没”FX应用程序线程。

您可以使用TaskAPI解决所有这些问题。Task类是Runnable的实现,其call()方法是从run()方法调用的。它有各种updateXXX方法,包括updategres(),它们更新FX Application线程上的各种属性,并限制这些调用,以便不超过FX Application线程可以处理的调度。最后,它有回调方法,例如setOn成功(),当后台工作完成时(或者,通常,当任务更改其生命周期状态时)在FX Application Thread上调用。

(注意:我重命名了您的类,使它们符合推荐的命名约定。与大多数Java开发人员一样,我发现阅读不符合这些约定的代码非常困难。)

public class Test {
    final ProgressBar bar = new ProgressBar(0.0);
    TextArea area = new TextArea();
    int result;
    final Worker work = new Worker();
    final Worker2 work2 = new Worker2();

// GUI Scene change happens here

    work.setOnSucceeded(e -> work2.doSomething(work.getValue(), area));
    bar.progressProperty().bind(work.progressProperty());
    new Thread(work).start();

}
public class Worker extends Task<Integer> {
    @Override
    protected Integer call(){
        for(int i = 1; i <= 1000000; i++)
            updateProgress(i, 1000000); 
        return 51;
    }
}
public class Worker2{
    ArrayList<String> list = new ArrayList<>();

    // this is now executed on the FX Application Thread: there is no need 
    // for Platform.runLater():

    public int doSomething(int index, TextArea area){
            area.append(list.get(index)); 
    }
}

您的第二个示例稍微复杂一点,我不确定您是否需要额外的线程:您的< code>Worker3似乎做的唯一一件事就是在文本区域追加一行,这无论如何都必须在FX应用程序线程上完成。但是,如果您的实际应用程序需要为每个文件做后台工作,这就是它看起来的样子。我建议使用任务池,而不是手工创建这么多任务。这将类似于:

public class Test {

    final ProgressBar bar = new ProgressBar(0.0);
    TextArea area = new TextArea();
    int result;
    final Worker work = new Worker();
    final Worker2 work2 = new Worker2();

    final ExecutorService exec = Executors.newCachedThreadPool();

    // This already has values
    // it is not really a file array list in the map, but it is easier to show it this way
    Map<String, ArrayList<File>> mapTypes; 

// GUI Scene change happens here

    work.setOnSucceeded(e -> {
        work2.doSomething(work.getValue(), area);

        Task<Void> processAllFiles = new Task<Void>() {
            @Override
            protected Void call() throws Exception {
                final CountDownLatch latch2 = new CountDownLatch(Map.keySet().size());
                mapTypes.keySet().forEach((String s) -> {
                    exec.submit(() -> {
                        work3.doSomething(mapTypes.get(s), area);
                        latch2.CountDown();
                    });
                });
                latch2.await();
                return null ;
            }
        };

        processAllFiles.setOnSucceeded(evt -> {
            // executed on fx application thread:
            System.out.println("Done");
        });
    });

    bar.progressProperty().bind(work.progressProperty());
    exec.submit(work);
}
 类似资料:
  • 我知道使用无线程异步有更多线程可用于服务输入(例如HTTP请求),但我不明白当异步操作完成并且需要一个线程来运行它们的延续时,这如何不可能导致线程饥饿。 假设我们只有3个线程 并且它们在需要线程的长时间运行的操作中被阻塞(例如在单独的db服务器上进行数据库查询) 使用async Wait,您可以 然而,在我看来,这可能会导致“正在进行”的异步操作过剩,如果太多的操作同时完成,那么就没有线程可以运行

  • 按下按钮后,我的界面冻结。我使用线程,但我不知道为什么仍然挂起。任何帮助都将不胜感激。提前谢谢

  • 问题内容: 我正在为我的ubuntu服务器(针对我的多客户端匿名聊天程序)实现一种简单的线程池机制,并且需要使我的工作线程进入睡眠状态,直到需要执行一项工作(以函数指针和参数的形式) 。 我当前的系统即将关闭。我(工人线程正在)问经理是否有工作可用,以及是否有5毫秒没有睡眠。如果存在,请将作业添加到工作队列中并运行该函数。糟糕的循环浪费。 什么我 喜欢 做的是做一个简单的事件性的系统。我正在考虑有

  • 我在使用JavaFX和线程时遇到问题。基本上我有两个选择:使用或。据我所知,应用于简单/简短的任务,而应用于较长的任务。然而,我不能使用它们中的任何一个。 当我调用时,它必须在任务执行过程中弹出一个验证码对话框。在使用任务时,它忽略了我显示新对话框的请求。。。它不允许我创造一个新的舞台。 另一方面,当我使用,它允许我显示一个对话框,但是,程序的主窗口会冻结,直到显示弹出对话框。 我需要任何解决办法

  • 我正在我的UI线程中调用一个方法。在这个方法中创建了一个新线程。我需要UI线程等待这个新线程完成,因为我需要这个线程的结果来继续UI线程中的方法。但我不想让UI在等待时冻结。有没有办法让UI线程在不忙的情况下等待?。

  • 问题内容: 我有以下情况: 为了运行算法,我必须运行多个线程,并且每个线程都会在死之前设置一个实例变量x。问题是这些线程不会立即返回: 我应该使用等待通知吗?还是我应该嵌入一个while循环并检查是否终止? 感谢大家! 问题答案: 创建一些共享存储来保存每个线程的值,或者如果足够的话,只存储总和。使用a 等待线程终止。每个线程完成后都会调用,您的方法将使用该方法来等待它们。 编辑: 这是我建议的方