2014-09-28 22 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

相關問題