【问题标题】:JavaFX make threads wait and threadsave gui updateJavaFX 使线程等待和线程保存 gui 更新
【发布时间】:2018-03-11 16:01:48
【问题描述】:

所以我有这样的事情:

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 在切换场景时开始滞后 - 意思是 - 整个线程 1 自行处理,然后 gui 加载了所有内容。经过一番研究,我认为这是因为主线程正在处理 runLater “请求”,并且由于 await() 主线程必须等到第一个辅助线程到达 倒计时().

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

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

提前谢谢你

使用的技术: - NetBeans
- JavaFX(无 FXML - 代码中设计的所有内容)
- CSS
- Java(显然)
- Windows 10 专业版

【问题讨论】:

    标签: java multithreading javafx


    【解决方案1】:

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

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

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

    您可以使用Task API 解决所有这些问题。 Task 类是Runnable 的实现,其call() 方法是从run() 方法调用的。它有各种updateXXX 方法,包括updateProgress(),这些方法可以更新 FX 应用程序线程上的各种属性,并限制这些调用,以便调度的数量不会超过 FX 应用程序线程可以处理的数量。最后,它有回调方法,例如setOnSucceeded(),当后台工作完成时(或者,一般来说,当任务更改其生命周期状态时)在 FX 应用程序线程上调用。

    (注意:我重命名了您的类,以便它们符合推荐的命名约定。像大多数 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)); 
        }
    }
    

    您的第二个示例稍微复杂一些,我不确定您是否需要额外的线程:您的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);
    }
    

    【讨论】:

    • 首先,非常感谢。这是一个制作精良的答案,经过深思熟虑。在过去的三天里,我一直坐在这个上面,这对我很有帮助。只是一点点跟进:work.setOnSucceeded(e -&gt; work2.doSomething(work.getValue(), area)); 这一行中的 setOnSucceeded 是什么?为什么work2现在在主线程中执行?
    • @Lukas 从答案中引用:“最后,它具有回调方法,例如在后台工作完成时在 FX 应用程序线程上调用的 setOnSucceeded()”。另外,您应该阅读我链接的文档:它描述了Task 中的所有方法。
    猜你喜欢
    • 2012-10-03
    • 1970-01-01
    • 2013-05-18
    • 2019-09-14
    • 1970-01-01
    • 2013-07-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多