java - 对于 JavaFX Canvas 多线程,我应该使用什么方法?

标签 java multithreading javafx-2

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

我工作的限制是:

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

到目前为止,我的方法是使用 Canvas JavaFX 节点作为可视化控件,并让接收线程安排对 Canvas 的更新,以便稍后在 JavaFX 应用程序线程中运行,就像这样。

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

我想到了一些可以加快速度的方法:

  1. 使用 WritableImage 而不是 Canvas 进行可视化。美中不足的是WritableImage/PixelWriter似乎没有太多的绘图方法,比如连fillRect都没有。我认为我必须实现自己的版本,而且我的版本可能会更慢。
  2. 拥有处理传入数据的线程所拥有的 Canvas 对象。从该 Canvas 复制到 Canvas ,该 Canvas 是 JavaFX 应用程序线程中场景图中的一个节点。复制可能会使用这些行的代码完成 sceneCanvas.getGraphicsContext2D().drawImage(processingCanvas.snapshot(SnapshotParameters(), null) 0, 0);。这样做的缺点是我认为它不是线程安全的,而且快照调用似乎相对昂贵。
  3. 在处理传入数据的线程中呈现 AWT BufferedImage,然后使用 SwingFXUtils.toFXImage() 从 BufferedImage 复制到 Canvas。这样做的缺点是线程语义似乎不清楚,使用 AWT 似乎有点傻。

您能否提出一些可能的方法?

谢谢!

最佳答案

我认为,主要问题是您的代码将太多绘图任务推送到 FX 应用程序线程的队列中。通常,每秒有 60 次绘图操作就足够了,这等于您的显示器的刷新率。如果你得到比这更多的“传入数据”事件,你会比必要的更频繁地绘制,浪费 CPU。因此,您必须将数据处理与绘画分离。

一种解决方案是使用 AnimationTimer。它的handle 方法会在每个动画帧中被调用,所以通常每秒调用 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);
}

关于java - 对于 JavaFX Canvas 多线程,我应该使用什么方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26082732/

相关文章:

UCS-2 的 java.io.UnsupportedEncodingException

java - Android 通用图像加载器部分显示图像

java - 使用广播接收器多次打开 Activity

c# - 内存屏障如何影响数据的 “freshness”?

java - Java中的多线程

css - javafx 2,CSS 和焦点消失

javafx-2 - javafx,获取对应用程序线程的引用

java - 用java为nutch编写代码

java - 多线程for循环

javafx - 从 TreeView 中删除节点