2014-09-28 20 views
3

我正在编写一个JavaFX应用程序,它可以在套接字上接收数据点并实时显示它们。问题是JavaFX渲染速度太慢。我有一个Swing实现,运行速度够快,但我需要使用JavaFX。我应该使用什么方法来处理JavaFX Canvas多线程?

,我中工作的制约因素有:

  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而不是为可视化的画布。缺点是WritableImage/PixelWriter似乎没有很多绘图方法,例如它甚至没有fillRect。我想我将不得不实施我自己的版本,我的版本可能会变慢。
  2. 让Canvas对象拥有处理传入数据的线程。从该画布复制到作为JavaFX应用程序线程场景图形中节点的画布。该副本可能会沿着这些行的代码sceneCanvas.getGraphicsContext2D().drawImage(processingCanvas.snapshot(SnapshotParameters(), null) 0, 0);完成。这样做的缺点是我认为它不是线程安全的,并且快照调用似乎相对昂贵。
  3. 渲染到处理传入数据的线程中的AWT BufferedImage,然后使用SwingFXUtils.toFXImage()从BufferedImage复制到画布。这样做的缺点是线程语义看起来不太清楚,使用AWT看起来有点愚蠢。

你能提出一些潜在的方法吗?

谢谢!

+0

您可以在处理线程中创建FX画布,然后将其交给FX应用程序线程(通过Platform.runLater)以交换场景图中的“旧”画布。 – isnot2bad 2014-09-29 09:57:48

+0

您是否已经测量了您的画布绘制方法运行的时间? – isnot2bad 2014-09-29 09:58:47

+0

绘图方法相对较快,通常少于一毫秒。我发现最大的问题是代码使用了Platform.runLater()。增加每个Platform.runLater()调用中完成的工作可显着提高应用程序的吞吐量。我不确定这个问题是否是Platform.runLater()的开销,或者问题是JavaFX是否将脉冲调用与Platform.runLater()调用交错,从而导致更多的重绘。我仍然很好奇人们认为对于这样的应用来说,这是一个很好的架构方法。谢谢! – 2014-09-29 18:41:37

回答

2

我假设,主要问题是您的代码将太多绘图任务推入FX应用程序线程的队列中。通常,每秒有60个绘图操作就足够了,这等于显示器的刷新率。如果你得到了比这更多的“传入数据”事件,那么你会比所需要的更频繁地绘制CPU,浪费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); 
} 
+0

这将是伟大的方法!感谢您的建议 – 2014-09-29 21:20:40

相关问题