当我需要在JavaFX线程中执行不确定数量的工作而不阻塞用户界面时,可以使用此类

public class AsyncWhile {

    private final IntPredicate hook;
    private int schedCount = 0;
    private boolean terminated = false;
    private int callCount = 0;
    private static final int schedN = 1;

    public AsyncWhile(IntPredicate hook) {
        this.hook = hook;
        schedule();
    }

    public void kill(){
        terminated = true;
    }

    private void schedule(){
        while(schedCount < schedN){
            Platform.runLater(this::poll);
            schedCount++;
        }
    }

    private void poll(){
        schedCount--;
        if(!terminated){
            terminated = !hook.test(callCount++);
            if(!terminated){
                schedule();
            }
        }
    }
}


像这样

asyncWhile = new AsyncWhile(i -> {
    // return false when you're done
    //     or true if you want to be called again
});

// can asyncWhile.kill() should we need to




如果您需要一个更具体的示例,这里我一次从InputStream中读取一行,然后解析并显示从该行解析的图:

asyncWhile = new AsyncWhile(i -> {
    String line;
    try {
        if((line = reader.readLine()).startsWith(" Search complete.")){ // it so happens that this reader must be read in the JavaFX thread, because it automatically updates a console window
            return false;
        } else {
            Task<MatchPlot> task = new ParsePlotTask(line);
            task.setOnSucceeded(wse -> {
                plotConsumer.accept(task.getValue());
                // todo update progress bar
            });
            executorService.submit(task);
            return true;
        }
    } catch (IOException ex) {
        new ExceptionDialog(ex).showAndWait();
        return false;
    }
});






像这样链接runLater就像是被黑客入侵。解决此类问题的正确方法是什么? (通过“这种问题”,我的意思是,如果不是因为它的内容必须在JavaFX线程中运行而不会使UI无响应,则可以通过一个简单的while循环解决该问题。)

最佳答案

推荐的

通常,基于PartialResultsTask文档(依赖于Task调用)的Platform.runLater示例提供解决方案是解决此问题的标准方法。

备用

您可以使用BlockingDeque而不是调度runLater。在您的处理任务中,您仅使用常规的while循环执行耗时的过程,生成需要在JavaFX UI中表示的非UI模型对象,然后将这些非UI模型对象放入队列中。然后,您设置一个TimelineAnimationTimer来轮询队列,根据需要将其排干,并从队列中选取项目并在UI中表示它们。

此方法类似于(但有点不同):Most efficient way to log messages to JavaFX TextArea via threads with simple custom logging frameworks

在这种情况下,使用您自己的队列与使用隐式队列runLater调用并没有什么不同,但是,如果需要,您可以使用自己的队列对流程进行更多控制。不过,这是一个折衷方案,因为它增加了一些自定义代码和复杂性,因此可能仅使用Task中推荐的PartialResults示例,如果不符合您的需求,则可能要研究基于自定义队列的替代方法。

在旁边

附带说明,您可以使用前面链接的自定义日志记录框架来记录来自多个线程的控制台消息,以显示在UI中。这样,您就不必让reader.readLine调用在JavaFX UI上执行I / O,这是不推荐的。相反,让I / O在JavaFX UI线程之外执行,并在处理项目时调用日志记录框架以记录最终将出现在UI上的消息(日志记录框架内的内部机制负责确保JavaFX遵守穿线规则)。


  使用我的方法可以看到任何危险吗?


很抱歉,此处不具体。我不会直接回答这个问题,但是会切切且并非总是适用于您的方法,使用runLater可能会引起问题,多数情况下这不是问题,但需要考虑以下事项:


如果发送足够多的runLater调用快于处理速度,则最终您将耗尽内存,或者某些runLater调用将开始被忽略(取决于runLater系统的工作方式)。
对runLater的调用是顺序的,而不是优先级,因此,如果还有内部事件也在运行,例如处理UI事件,这些事件可能会在处理runLater调用时被延迟。
runLater无法保证稍后的时间。如果您的工作对时间敏感,则可能是一个问题,或者至少是您在实现中需要考虑的问题。
runLater系统内部可能相当复杂,除非您仔细研究源代码,否则您将无法确切知道它的实现方式。
您在runLater上运行的所有操作都将阻止JavaFX应用程序线程,可能直到所有未完成的runLater调用完成为止
发出一堆runLater调用后,就无法轻松地将它们的处理散布在JavaFX动画系统中的多个脉冲上,它们很可能全部在下一个脉冲上执行。因此,您必须注意不要一次发送太多电话。


这些只是一些想到的东西。

但总的来说,runLater是执行许多任务的可靠机制,并且是JavaFX体系结构的核心部分。对于大多数事情,以上考虑实际上并没有任何后果。

编写高质量的多线程代码非常棘手。在某种程度上,通常最好是尽可能避免这种情况,这是JavaFX系统试图通过使场景图形访问成为单线程来最大程度地做到的。如果必须执行此操作,则请遵循“任务”文档中概述的模式,或者尽可能多地利用一些高级java.util.concurrent系统,而不要实现自己的系统。还要注意,读取多线程代码比编写多线程代码更为棘手,因此请确保下一个人清楚您所做的工作。

08-08 01:21