我应该使用什么方法进行JavaFX Canvas多线程?

时间:2014-09-28 07:40:11

标签: java multithreading javafx-2

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

我正在进行的约束是:

  1. 只能通过JavaFX应用程序线程更新可视化的控件(我相信这是所有JavaFX和Swing应用程序都需要的。)
  2. 应该从人眼的角度平滑地更新可视化。每秒大约10次更新就足够了。每一秒都不够。
  3. 传入的数据速率足够高(每秒大约50个事件,在其他上下文中并不高),并且每个事件处理的成本非常高,以至于必须在JavaFX应用程序以外的线程中接收和处理传入数据线程,以便GUI不会阻塞(我相信这是许多GUI应用程序的一个常见要求)。
  4. 到目前为止,我的方法是使用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对象。从该画布复制到画布,该画布是JavaFX应用程序线程中场景图中的节点。副本可能是使用这些行sceneCanvas.getGraphicsContext2D().drawImage(processingCanvas.snapshot(SnapshotParameters(), null) 0, 0);的代码完成的。这样做的缺点是我认为它不是线程安全的,而且快照调用似乎相对昂贵。
    3. 渲染到处理传入数据的线程中的AWT BufferedImage,然后使用SwingFXUtils.toFXImage()从BufferedImage复制到Canvas。这样做的缺点是线程语义似乎不清楚,使用AWT似乎有点傻。
    4. 您能否提出一些可能的方法?

      谢谢!

1 个答案:

答案 0 :(得分:3)

我认为,主要问题是您的代码将太多的绘图任务推送到FX Application线程的队列中。通常,每秒进行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);
}