是否使用时间轴进行正确安排

ayv*_*ngo 5 javafx timer

我发现了很多建议使用TimelineKeyFrame.onFinished在计划任务的JavaFX。但是AnimationTimer文档说,它的handle方法每秒被调用 60 次。

从文档中不清楚,但似乎在内部Timeline使用AnimationTimer。这是否意味着调度的时间线解决方案强加了 CPU 密集型轮询模式?如果这实际上是 JavaFX 的工作方式,那么推荐其他哪些调度方法?

jew*_*sea 8

JavaFX 中调度的建议使用规则

鉴于时间轴的工作原理(见下文),可以推导出一些基本规则:

  1. 当您想要安排修改场景图属性的快速操作时,请使用时间轴

  2. 当您想要执行以下任何操作时,请勿使用时间轴

    1. 执行本身很耗时的操作(例如,给定计划活动的工作需要超过 1/30 秒)。
    2. 执行与场景图没有任何关系的工作。
    3. 以大于脉冲(1/60 秒)的分辨率进行实时调度。
    4. 使用传统的 java.util.concurrent 机制或第三方库控制您的调度。
    5. 在他们自己的线程上运行你的计划任务。

此外,对于在固定延迟后更新场景图的一次性执行,我喜欢使用PauseTransition

对特定问题和疑虑的回答

从文档中不清楚,但时间线似乎在内部使用 AnimationTimer。

不,时间轴在内部不使用AnimationTimer。但是,时间线仍然是基于脉冲的,并且将接收代码以更新时间线的当前状态,将在每个脉冲上调用(通常每秒 60 次)。要了解什么是脉冲,请阅读下面的背景部分。

这是否意味着调度的时间线解决方案强加了 CPU 密集型轮询模式?

是的,有一些开销,但它可能很小,可以忽略不计。不管您是否自己创建时间线,JavaFX 系统都会在每个脉冲上运行一些工作。除非您对对象进行大量动画处理或对每个脉冲进行大量工作,否则处理每个脉冲的时间将是微不足道的,因为对每个脉冲要做的工作很少。

如果这实际上是 JavaFX 的工作方式,那么推荐其他哪些调度方法?

Timeline 有许多替代方案:

  1. 使用javafx.concurrent工具,例如 Task 或 Service,特别是ScheduledService
    • 如果您想向 JavaFX UI 提供反馈(例如进度、消息更新和返回值),这很好。
  2. 使用java.util.concurrent工具。
    • 如果您不需要 javafx.concurrent 的附加功能来进行进度和消息传递,或者您想要 java.util.concurrent 设施提供的附加控制,那么这很好。
    • 如果您使用它,您仍然可以通过调用Platform.runLater()获得对 JavaFX UI 的间歇性或最终反馈。
  3. 使用java.util.TimerTask
    • 比 java.util.concurrent 更简单的 API,没有用于 JavaFX 反馈的直接接口,不过您可以再次使用 Platform.runLater() 来实现。
  4. 使用第三方调度系统(例如Quartz):
    • 比其他解决方案更复杂,但也更灵活,虽然它添加了一个依赖库并且如果你想修改场景图元素需要 Platform.runLater。

如果您确实使用时间线以外的其他内容,则需要注意:

  1. 如果要从非 JavaFX 应用程序线程修改场景图上的任何内容,请调用 Platform.runLater。
  2. 您不要太频繁地调用 Platform.runLater 以免使 JavaFX 应用程序线程过载(例如,通过合并更新调用)。

背景信息

时间线操作

例如,假设您有一个时间轴,它有一个持续时间为 1 秒的关键帧。内部时间线仍会在每个脉冲中更新。这是必需的,因为时间轴不仅仅是一个调度程序,它还具有其他属性和任务。例如,时间轴有一个currentTime属性。currentTime 将在每个脉冲中更新,以便它始终准确地反映当前时间。类似地,时间线的内部逻辑将检查是否有任何与时间线关联的KeyValues,并根据其关联的Interpolator在每个脉冲上更新它们的值. 内部逻辑将检查每个脉冲以查看 Timeline 是否已完成,以调用 onFinished 处理程序。它还将评估当前时间与每个关键帧的持续时间,如果匹配,则为关键帧触发适当的动作事件。

因此,时间线就像一个调度程序,因为它可以在特定时间执行关键帧,但它不仅仅是一个调度程序,因为它还维护它的当前时间、当前状态和不断改变相关键值的能力。此外,您可以改变时间线的速率、方向和周期数,以及将其暂停到位,然后再恢复,这也不同于传统的调度程序。

Timeline 的一个方面是因为它基于 JavaFX 系统中脉冲的回调;一切都在 JavaFX 应用程序线程上运行。因此,当您使用时间线(甚至 1000 个)时,不会产生额外的线程,因此从这个角度来看,它是轻量级的。此外,时间线调用的所有逻辑都发生在 JavaFX 应用程序线程上。因为一切都在 JavaFX 应用程序线程上运行,这使得时间轴非常适合操作活动 JavaFX 场景图的元素和属性(此类操作必须在 JavaFX 应用程序线程上执行)。但是,如果您想在相关的时间轴关键帧中执行大量耗时的 CPU 工作或阻塞 I/O,那么时间轴将是一个糟糕的选择。这是因为 JavaFX 应用程序线程将在该工作发生时被阻塞,从而冻结您的 UI。

脉冲

JavaFX 运行时系统基于脉冲

脉冲是一个事件,它向 JavaFX 场景图指示是时候将场景图上元素的状态与 Prism 同步了。脉冲以每秒 60 帧 (fps) 的最大速度进行节流,并在场景图上运行动画时触发。即使动画没有运行,当场景图中的某些内容发生更改时,也会安排一个脉冲。例如,如果按钮的位置发生变化,则安排一个脉冲。

当脉冲被触发时,场景图上元素的状态会同步到渲染层。脉冲使应用程序开发人员能够异步处理事件。这一重要功能允许系统在脉冲上批处理和执行事件。

JavaFX 动画系统存储所有动画动画计时器的列表,这些动画动画计时器已创建,但未被垃圾回收。在每个脉冲上,它将遍历此列表。每个动画计时器都会在每个脉冲上调用它的句柄回调。每个时间线或过渡将类似地更新其内部状态,并根据需要更新任何关联的关键值或触发关键帧动作事件。