JavaFX Canvas多线程应使用哪种方法?
作者:互联网
我正在编写一个JavaFX应用程序,该应用程序在套接字上接收数据点并实时可视化它们.问题在于JavaFX渲染太慢.我有一个运行速度足够快的Swing实现,但是我需要改用JavaFX.
我正在研究的约束是:
>可视化控件只能由JavaFX应用程序线程更新(我相信这对于所有JavaFX和Swing应用程序都是必需的).
>应该从人眼的角度平滑更新可视化效果.每秒大约10次更新就足够了.每秒一次是不够的.
>传入数据速率足够高(每秒约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(...);
}});
}
我想到了一些可以加快速度的方法:
>使用WritableImage而不是Canvas进行可视化.缺点是WritableImage / PixelWriter似乎没有很多绘制方法,例如,它甚至没有fillRect.我想我必须实现自己的版本,而我的版本可能会变慢.
>具有处理输入数据的线程所拥有的Canvas对象.从该画布复制到JavaFX应用程序线程中场景图中节点的画布.复制可能会通过以下代码完成:sceneCanvas.getGraphicsContext2D().drawImage(processingCanvas.snapshot(SnapshotParameters(),null)0,0);.缺点是我认为它不是线程安全的,并且快照调用似乎相对昂贵.
>渲染到处理输入数据的线程中的AWT BufferedImage,然后使用SwingFXUtils.toFXImage()从BufferedImage复制到Canvas.缺点是线程语义似乎不清楚,使用AWT似乎有点愚蠢.
您能提出一些潜在的方法吗?
谢谢!
解决方法:
我认为,主要问题是您的代码将太多的绘图任务推入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);
}
标签:multithreading,javafx-2,java 来源: https://codeday.me/bug/20191121/2050083.html