简体   繁体   English

JavaFX使线程等待并且threadave gui更新

[英]JavaFX make threads wait and threadsave gui update

So I had something like this: 所以我有这样的事情:

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

And everything worked fine, until I had to wait for something else to finish before I could continiue after the runnable so I altered it like this: 一切正常,直到我不得不等待其他事情完成之后才能继续运行,所以我这样修改了它:

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

later I do something like this: 后来我做这样的事情:

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

Now my gui started to lag when switching scenes - meaning - the whole thread1 processed itself and then the gui loaded everything. 现在,我的GUI在切换场景时开始滞后-意思是-整个线程1自行处理, 然后 gui加载了所有内容。 After researching a bit I think this happens because the main thread is handeling the runLater "requests" and because of the await() the main thread has to wait until the first secondary thread comes to the CountDown() . 经过研究后,我认为这是因为主线程处理了runLater的“请求”,并且由于await() ,主线程不得不等到第一个辅助线程到达CountDown()

My question is, how do I manage that the main thread isn't starting the second background thread before the first one finished? 我的问题是,如何管理主线程在第一个后台线程完成之前没有启动第二个后台线程? Bonus question: what would be a more efficient way to update my GUI instead of Plattform.runlater() ? 额外的问题:什么是更新我的GUI而不是Plattform.runlater()的更有效方法?

Note: 注意:
I have also looked at this Question but it does not fully address my Problem as I do not need to queue threads. 我也看过这个问题,但是由于我不需要排队线程,因此它不能完全解决我的问题。 I much more need to know how to make the main thread wait until the sub thread is finished and only then proceeds. 我还需要知道如何使主线程等到子线程完成后才继续执行。 The main thread, however, must not be completly inactive but manage the incoming update request. 但是,主线程不得完全处于非活动状态,而应管理传入的更新请求。

Thank you in advance 先感谢您

Technologies used: - NetBeans 使用的技术:-NetBeans
- JavaFX ( No FXML - everything designed in the code) -JavaFX(无FXML-代码中设计的所有内容)
- CSS -CSS
- Java ( Obviously ) -Java(显然)
- Windows 10 pro -Windows 10专业版

The (main) problem with your code is that you are calling latch.await() , which is a blocking method, on the JavaFX Application Thread. 代码的(主要)问题是您正在JavaFX Application Thread上调用latch.await() ,这是一种阻塞方法。 Since the JavaFX Application Thread is responsible for updating the UI, this prevents the UI from being redrawn until latch.await() releases. 由于JavaFX Application Thread负责更新UI,因此可以防止在latch.await()释放之前重新绘制UI。

The basic premise of your question is wrong: you never want to make the UI thread pause, as it will always render the UI unresponsive and prevent any updates. 这个问题的基本前提是错误的:您永远都不想使UI线程暂停,因为它总是使UI无响应并阻止任何更新。 Instead, you should think in terms of "performing a unit of work" in the background, potentially with updates to the UI as it proceeds, and then doing something in response to the background work completing. 相反,您应该在后台考虑“执行工作单元”,可能会在UI进行时对UI进行更新,然后根据后台工作的完成进行一些操作。

Another potential issue with your code is that you are submitting a vast number of Runnable s to the FX Application Thread via Platform.runLater() . 代码的另一个潜在问题是,您正在通过Platform.runLater()向FX应用程序线程提交大量Runnable You probably need to throttle these so that they don't "flood" the FX Application Thread. 您可能需要限制它们,以免它们“淹没” FX应用程序线程。

You can solve all these issues using the Task API . 您可以使用Task API解决所有这些问题。 The Task class is an implementation of Runnable whose call() method is invoked from the run() method. Task类是一个实现Runnable ,其call()方法是从调用run()方法。 It has various updateXXX methods, including updateProgress() , that update various properties on the FX Application thread, and throttle these calls so that no more are scheduled than the FX Application thread can handle. 它具有各种updateXXX方法,包括updateProgress() ,它们可以更新FX Application线程上的各种属性,并限制这些调用,以便调度的时间不会超过FX Application线程可以处理的范围。 Finally, it has callback methods, such as setOnSucceeded() that are invoked on the FX Application Thread when the background work completes (or, generally, when the task changes its lifecycle state). 最后,它具有回调方法,例如setOnSucceeded() ,在后台工作完成时(或通常在任务更改其生命周期状态时)在FX Application Thread上调用。

(Note: I renamed your classes so they conform to recommended naming conventions. Like most Java developers, I find it extremely difficult to read code that doesn't conform to these.) (注意:我重命名了您的类,使它们符合建议的命名约定。像大多数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)); 
    }
}

Your second example is a little more complicated, and I'm not really sure you need the additional threads at all: the only thing your Worker3 seems to do is append a line to a text area, which has to be done on the FX Application Thread anyway. 您的第二个示例稍微复杂一点,我不确定您是否根本需要其他线程: Worker3似乎唯一要做的就是在文本区域中添加一行,这必须在FX Application上完成。无论如何还是线程。 But in case your real application would need background work for each file, this is what it would look like. 但是,如果您的实际应用程序需要每个文件的后台工作,则外观将是这样。 I would recommend using a task pool for this instead of creating so many tasks by hand. 我建议为此使用任务池,而不要手动创建这么多任务。 This would look something like: 这看起来像:

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

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

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