当前位置: 首页 > 知识库问答 >
问题:

对于JavaFX画布多线程,我应该使用什么方法?

屠盛
2023-03-14

我正在编写一个JavaFX应用程序,它在套接字上接收数据点并实时可视化它们。问题是JavaFX渲染太慢了。我有一个运行速度足够快的Swing实现,但我需要使用JavaFX代替。

我工作的限制条件包括:

  1. 可视化控件只能由JavaFX应用程序线程更新(我相信这对于所有JavaFX和Swing应用程序都是必需的)
  2. 从人眼的角度来看,可视化应该能够顺利更新。大约每秒10次更新就足够了。每秒钟一次是不够的
  3. 传入数据速率足够高(大约每秒50个事件,在其他上下文中没有那么高),并且每个事件处理的成本足够高,因此传入数据必须在JavaFX应用程序线程以外的线程中接收和处理,以便GUI不会阻塞(我相信这是许多GUI应用程序的常见要求)

到目前为止,我的方法是使用画布JavaFX节点作为可视化控件,并让接收线程计划对画布的更新,以便稍后在JavaFX应用程序线程中运行,如下所示。

    public void onEvent(Event event) {
        ....do processing... 
        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                graphics.setFill(...);
                graphics.fillRect(...);
                }});
    }

我想到了一些可以加速这一进程的方法:

  1. 使用可写图像而不是画布进行可视化。缺点是WritableImage/PixelWriter似乎没有很多绘图方法,例如它甚至没有fillRect。我想我必须实现自己的版本,我的版本可能会更慢
  2. 拥有处理传入数据的线程所拥有的画布对象。将该画布复制到JavaFX应用程序线程中场景图中的节点画布。复制可能会使用这些行的代码来完成。getGraphicsContext2D()。drawImage(processingCanvas.snapshot(SnapshotParameters(),null)0,0) 。这样做的缺点是,我认为它不是线程安全的,而且快照调用似乎相对昂贵
  3. 在处理传入数据的线程中渲染到AWT BuffereImage,然后使用SwingFXUtils从BuffereImage复制到画布。toFXImage()。这样做的缺点是线程语义似乎不明确,使用AWT似乎有点愚蠢

你能提出一些可能的方法吗?

谢谢你!

共有1个答案

景同
2023-03-14

我假设,主要问题是代码将太多的绘图任务推到FX应用程序线程的队列中。通常,每秒60次绘图操作就足够了,这相当于监视器的刷新率。如果您得到的“传入数据”事件比这多,那么绘制的频率将超过需要,从而浪费CPU。因此,必须将数据处理与绘制分离。

一种解决方案是使用AnimationTimer。它的句柄方法将在每个动画帧中调用,因此通常每秒60次。动画计时器在处理新数据的情况下处理重绘。

// generic task that redraws the canvas when new data arrives
// (but not more often than 60 times per second).
public abstract class CanvasRedrawTask<T> extends AnimationTimer {
    private final AtomicReference<T> data = new AtomicReference<T>(null);
    private final Canvas canvas;

    public CanvasRedrawTask(Canvas canvas) {
        this.canvas = canvas;
    }

    public void requestRedraw(T dataToDraw) {
        data.set(dataToDraw);
        start(); // in case, not already started
    }

    public void handle(long now) {
        // check if new data is available
        T dataToDraw = data.getAndSet(null);
        if (dataToDraw != null) {
            redraw(canvas.getGraphicsContext2D(), dataToDraw);
        }
    }

    protected abstract void redraw(GraphicsContext context, T data);
}

// somewhere else in your concrete canvas implementation
private final RedrawTask<MyData> task = new RedrawTask<MyData>(this) {
    void redraw(GraphicsContext context, MyData data) {
        // TODO: redraw canvas using context and data
    }
}

// may be called by a different thread
public void onDataReceived(...) {
    // process data / prepare for redraw task
    // ...

    // handover data to redraw task
    task.requestRedraw(dataToDraw);
}
 类似资料:
  • 问题内容: 我有2个线程T1和T2,两者都有不同的工作,因此通常我们更喜欢通过线程Joins完成此任务。 但是我们无需使用join()就可以做到这一点。我们可以在T1线程中添加T2线程的代码。这有什么区别? 问题答案: 主要区别在于,当我们将T2线程与T1连接在一起时,T2执行该任务的时间也可以由T1占用,这意味着它们将并行执行不同的任务。但是,当您在T1中包含T2线程代码时,不会发生这种情况。线

  • 问题内容: 我已经在Android SDK中看到了AccountManager,它用于存储帐户信息。因此,我找不到任何针对其用途的一般性讨论。有人知道AccountManager的目的是什么以及它能给您带来什么好处吗?对于适合哪种类型的帐户有任何意见?您将在这里将用户的帐户信息用于常规Web服务吗? 问题答案: 这个问题有点老了,但我认为它仍然值得关注。 ,然后一起去。 你不能有一个没有。 你不能

  • 对于与PropertyValueFactory相关的问题,许多回答(和评论)建议避免使用该类和其他类似类。使用这个类有什么问题?

  • 问题内容: 之间有什么区别: 和 我知道JPanel是GUI组件的容器,但我确实看不到使用它的实用程序。当然,我错了,但我是从Swing开始的,所以…为什么我应该使用JPanel?真正的目的是什么? 问题答案: 为什么我应该使用JPanel? 您可以使用JPanel获得以下一项或多项好处: 将组件分组在一起。 为了更好地组织您的组件。 为了使我们能够使用 多种布局 并组合其效果。(例如,用于数字键

  • 当我创建一个简单的非多线程JavaFX应用程序并启动它时,该应用程序会创建一些线程(JavaFXApplicationThread、JavaFXLauncher等)。这些线程中的大多数都已命名,但在我的所有JavaFX应用程序中都有一个未命名的线程(“线程-1”或“线程-2”)。我绝对不会创建自己的线程,因为我尝试启动Hello World JavaFX应用程序(由IDEA生成),其中也包含“线程

  • 我试着在画布里放一幅画。但就是没有出现。我谷歌了一下,看看哪里出了问题。不幸的是,我还是解决不了。这是我的代码 这是css文件