【问题标题】:"Asynchronous while loop" in JavaFX threadJavaFX线程中的“异步while循环”
【发布时间】:2016-01-13 23:16:21
【问题描述】:

当我需要在 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;
    }
});

)


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

【问题讨论】:

  • 我不太确定如何回答您的问题:我将使用 Task documentation 中的 PartialResultsTask 示例来解决您最后描述的示例问题。但请注意,您的 AsyncWhile 类中存在细微的线程错误:terminated 需要声明为 volatile,因为它是从多个线程访问的,并且 schedCount 需要在 synchronized 块中访问,或者需要替换为AtomicInteger。似乎您正在尝试重新发明轮子...
  • @James_D 如果列出的所有代码都在 JavaFX 线程中运行,如何从多个线程访问 terminated
  • 您必须假设 kill() 只从 FX 应用程序线程调用。您的代码不会强制执行此操作,但如果这是真的,那么 terminated 不需要是 volatile。不过,鉴于您编写此类的一般情况,并且鉴于 kill() 不太可能是性能关键操作,您可能应该使该线程安全。
  • @James_D 这是一个单线程类。如果其中有任何细微的错误,我将不胜感激。
  • 好吧,我当时误会了。我假设(因为你已经调用了Platform.runLater(...))这是打算从后台线程中使用的。

标签: java multithreading asynchronous javafx javafx-8


【解决方案1】:

推荐

通常,基于Task 文档中的PartialResultsTask 示例(依赖于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 可能会导致问题,主要不是问题,但需要考虑一些事项:

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

这些只是我想到的一些事情。

但总的来说,runLater 是一种适用于许多任务的可靠机制,也是 JavaFX 架构的核心部分。对于大多数事情,上述考虑实际上并没有任何后果。

编写高质量的多线程代码非常棘手。在可能的情况下,它通常最好避免,这是 JavaFX 系统通过使场景图访问单线程来尝试做的大部分工作。如果您必须这样做,那么请遵循任务文档中概述的模式或尽可能地利用一些高级 java.util.concurrent 系统,而不是实现您自己的系统。另请注意,阅读多线程代码甚至比编写它更棘手,因此请确保下一个人清楚您所做的事情。

【讨论】:

  • 谢谢,我会看看这些选项。我曾考虑过Timeline/AnimationTimer,但回调的频率应该适应处理器的可用性(JavaFX 线程)。你能看出使用我的方法有什么危险吗? (我担心(无论是否合理)是,如果由于某种或其他原因没有发生runLater,那么整个链条就会断裂。这就是为什么我会考虑schedN&gt;1,这样链条就有多个独立的链接无处不在。)
  • 您能否详细说明“回调的频率应适应 FX 应用程序线程的处理器可用性”?据我了解,AnimationTimer 为您提供了该功能。
  • 啊.. 我这么说是因为我假设它们都会执行固定间隔回调。通过一次安排一个runLater,我假设它(而不是全部)将在线程当前想要做的任何事情完成后被调用。到目前为止,AsyncWhile 在实践中似乎效果很好。除了 UI 保持响应之外,出于某种奇怪的原因,甚至完成时间也比正常的 while 循环快得多。感谢您的所有见解和指示,我仍会更仔细地查看。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-01-28
  • 1970-01-01
  • 1970-01-01
  • 2015-06-14
  • 2014-05-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多