【问题标题】:JavaFX thread crashesJavaFX 线程崩溃
【发布时间】:2014-01-20 10:31:29
【问题描述】:

我有一个 JavaFX 应用程序,它可以在屏幕上为机器人(黑点)设置动画,并在它们所在的任何地方(想想 Tron)在浅灰色背景上绘制一条小白线。为此,我保留了机器人和所有白色像素的所有坐标。机器人的行为由实现 Runnable 的不同线程控制,并且可以在模拟运行时更改。机器人坐标存储在 HashMap 中,其中 Coordinates 是一个扩展 Point 的类,并使用双精度来提高 x 和 y 值的内部计算精度。对于白点,我使用 HashMap 作为整数精度对它们来说就足够了,因为它们不会移动并不确定地停留在屏幕上的那些 x 和 y 坐标上。

现在程序运行得很好,但是当存储白点点的 HashMap 增长时,应用程序的 JavaFX 线程崩溃的可能性越来越大(仔细想想,更具体地说,它只是机器人所在的画布绘制。)控件的滑块保持响应,迭代的文本字段和 HashMap 的大小不断更新。但是没有任何动画,几秒钟后画布变成白色。增加 Thread.sleep(ms) 的 ms 会使程序更稳定,但它已经很慢了。此外,在我的慢速学校上网本(运行 Win XP)上,它比在我的家用台式电脑(运行 Win7 64 位)上更频繁、更早地发生。例外也是不同的。对于台式电脑,如下所示:

java.lang.InternalError: Unrecognized PGCanvas token: 68
at com.sun.javafx.sg.prism.NGCanvas.renderStream(NGCanvas.java:651)
at com.sun.javafx.sg.prism.NGCanvas.renderContent(NGCanvas.java:320)
at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:187)
at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:39)
at com.sun.javafx.sg.BaseNode.render(BaseNode.java:1145)
at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:204)
at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:420)
at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:187)
at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:39)
at com.sun.javafx.sg.BaseNode.render(BaseNode.java:1145)
at com.sun.javafx.tk.quantum.ViewPainter.doPaint(ViewPainter.java:117)
at com.sun.javafx.tk.quantum.AbstractPainter.paintImpl(AbstractPainter.java:175)
at com.sun.javafx.tk.quantum.PresentingPainter.run(PresentingPainter.java:73)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
at java.util.concurrent.FutureTask$Sync.innerRunAndReset(FutureTask.java:351)
at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:178)
at com.sun.prism.render.RenderJob.run(RenderJob.java:37)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at com.sun.javafx.tk.quantum.QuantumRenderer$PipelineRunnable.run(QuantumRenderer.java:98)
at java.lang.Thread.run(Thread.java:724)

对于上网本是:

java.lang.IllegalArgumentException: alpha value out of range
at java.awt.AlphaComposite.<init>(AlphaComposite.java:624)
at java.awt.AlphaComposite.getInstance(AlphaComposite.java:689)
at java.awt.AlphaComposite.derive(AlphaComposite.java:761)
at com.sun.prism.j2d.J2DPrismGraphics.setExtraAlpha(J2DPrismGraphics.java:569)
at com.sun.javafx.sg.prism.NGCanvas.renderStream(NGCanvas.java:739)
at com.sun.javafx.sg.prism.NGCanvas.renderContent(NGCanvas.java:389)
at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:201)
at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:40)
at com.sun.javafx.sg.BaseNode.render(BaseNode.java:1145)
at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:204)
at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:420)
at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:201)
at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:40)
at com.sun.javafx.sg.BaseNode.render(BaseNode.java:1145)
at com.sun.javafx.tk.quantum.ViewPainter.doPaint(ViewPainter.java:117)
at com.sun.javafx.tk.quantum.AbstractPainter.paintImpl(AbstractPainter.java:182)
at com.sun.javafx.tk.quantum.PresentingPainter.run(PresentingPainter.java:73)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
at java.util.concurrent.FutureTask$Sync.innerRunAndReset(FutureTask.java:351)
at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:178)
at com.sun.prism.render.RenderJob.run(RenderJob.java:37)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
at com.sun.javafx.tk.quantum.QuantumRenderer$PipelineRunnable.run(QuantumRenderer.java:108)
at java.lang.Thread.run(Thread.java:722)

任何帮助解决或缩小此问题的范围将不胜感激。

编辑: 正如建议的那样,我将添加一个仍然存在问题的代码的最小示例。主要方法位于绘制模拟区域的 Visualizer 类中

import java.awt.Point;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import javafx.application.Application;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.stage.Screen;
import javafx.stage.Stage;

public class Visualizer extends Application {

    private static GraphicsContext gc;
    private static Canvas canvas;
    private static BorderPane pane;
    private static Scene scene;
    private static Thread thread;
    private static Simulator sim = new Simulator();
    private static int optionsWidth = 200;
    private static HashSet<Point> manganCollected = new HashSet<Point>();
    private static HashMap<Integer, Coordinates> coordinates = new HashMap<Integer, Coordinates>();
    private static final int zoom = 4;
    private static Point cmpP;
    /**
     * Height in pixels that's available to draw the simulation on dependent on used monitor resolution 
     */
    public static double simHeight;
    /**
     * Width in pixels that's available to draw the simulation on dependent on used monitor resolution 
     */
    public static double simWidth;


    /**
     * Launches the JavaFX application
     * @param args command line arguments if there are any
     */
    public static void main(String[] args) {
        launch(args);   
    }

    /**
     * Sets up the GUI with all options and the canvas to draw the robots on 
     */
    @Override
    public void start(Stage stage) throws Exception {

        // Determine screen width of the monitor
        Screen screen = Screen.getPrimary();
        Rectangle2D bounds = screen.getVisualBounds();
        double screenHeight = bounds.getHeight();
        double screenWidth = bounds.getWidth();

        // set the stage
        stage.setFullScreen(true);
        stage.setHeight(screenHeight);
        stage.setWidth(screenWidth);
        stage.setTitle("Manganernte");

        // Canvas to draw the simulation on 
        canvas = new Canvas();
        simHeight = screenHeight;
        simWidth = screenWidth - optionsWidth;
        canvas.setHeight(simHeight);
        canvas.setWidth(simWidth);
        gc = canvas.getGraphicsContext2D();
        gc = canvas.getGraphicsContext2D();
        gc.setFill(Color.LIGHTGRAY);
        gc.fillRect(0, 0, simWidth, simHeight);
        gc.setStroke(Color.BLACK);
        gc.setLineWidth(3);
        gc.strokeRect(0, 0, simWidth, simHeight);

        // BorderPane containing the buttons box and the Simulator canvas
        pane = new BorderPane();
        pane.setCenter(canvas);

        // Scene containing the pane
        scene = new Scene(pane);

        // Show the whole stage
        stage.setScene(scene);
        stage.show();
        thread = new Thread(sim);
        thread.start();
    }

    /**
     * Transforms double coordinates as used by the simulator (0/0 in the center) to monitor coordinates (0/0 top left corner)
     * @param coordinates Floating point coordinates that should be transformed
     * @return Coordinates Floating point coordinates that have been transformed
     */
    private static Coordinates transform(Coordinates coordinates) {
        return new Coordinates(Math.round((simWidth / 2) + (zoom * coordinates.getX())), Math.round((simHeight / 2) + (zoom * coordinates.getY()))); 
    }

    /**
     * Transforms integer coordinates as used by the simulator (0/0 in the center) to monitor coordinates (0/0 top left corner)
     * @param Point Integer coordinates that should be transformed
     * @return Point Integer coordinates that have been transformed
     */
    private static Point transform(Point point) {
        return new Point((int)Math.round((simWidth / 2) + (zoom * point.getX())), (int)Math.round(((simHeight / 2) + (zoom * point.getY())))); 
    }

    /**
     * Clear the canvas by drawing a rectangle filled with light gray background
     */
    private static void clear () {
        gc.setFill(Color.LIGHTGRAY);
        gc.fillRect(0, 0, simWidth, simHeight);
        gc.setStroke(Color.BLACK);
        gc.setLineWidth(3);
        gc.strokeRect(0, 0, simWidth, simHeight);
    }

    /**
     * Clears the canvas and then draws first the collected mangan as white rectangles followed by robots as black circles the given coordinates
     * @param redraw boolean that's set to true if iteration hasn't changed but a redraw should be forced anyway (e.g. when simulation is paused and the zoom is used)
     */
    public static void DrawRobots() {
        coordinates = Simulator.coordinates;
        manganCollected = Simulator.manganCollected;
        // clear the canvas with light gray background
        clear();
        // draw harvested mangan as white dots
        gc.setFill(Color.WHITE);
        Iterator<Point> it = manganCollected.iterator(); 
        while(it.hasNext()) {
            cmpP = it.next().getLocation();
            double x = transform(cmpP).getX();
            double y = transform(cmpP).getY();
            gc.fillRect(x, y, zoom, zoom);
        }
        // draw robots
        gc.setFill(Color.BLACK);
        for(int i = 1; i <= coordinates.size(); i++) {
            double x = transform(coordinates.get(i)).getX();
            double y = transform(coordinates.get(i)).getY();
            gc.fillOval(x, y, zoom, zoom);
        }
    }
}

模拟器类:

import java.awt.Point;
import java.util.HashMap;
import java.util.HashSet;

public class Simulator implements Runnable {

    // start variable declarations
    // HashMap of all robot objects the simulator controls
    private static HashMap<Integer, Robot> robots = new HashMap<Integer, Robot>();
    // HashMap of all coordinate objects the simulator controls
    public static HashMap<Integer, Coordinates> coordinates = new HashMap<Integer, Coordinates>();
    // HashMap of all point objects containing the coordinates of places where the mangan has already been collected
    public static HashSet<Point> manganCollected = new HashSet<Point>();
    /**
     * communication radius of the robots according to the requirements
     */
    public static int processSpeed = 100;
    // end variable declarations

    /**
     * Create a robot with x and y
     * @param x x-coordinate
     * @param y y-coordinate
     */
    public static void createRobot(int x, int y) {
        coordinates.put(coordinates.size() + 1, new Coordinates(x, y));
        robots.put(robots.size() + 1, new Robot());
    }

    /**
     * Checks the status, changes it if necessary and acts accordingly
     */
    @Override
    public void run() {
        for(int i = 0; i < 100; i++) createRobot(i - 50, 0);
        Visualizer.DrawRobots();
        while(true) {
            for(int i = 1; i <= robots.size(); i++) robots.get(i).think();
            for(int i = 1; i <= robots.size(); i++) {
                coordinates.get(i).add(robots.get(i).move());
                manganCollected.add(new Point((int)Math.round(coordinates.get(i).getX()), (int)Math.round(coordinates.get(i).getY())));
            }
            Visualizer.DrawRobots();
            try {
                Thread.sleep(processSpeed);
            } catch(InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

最后是在这个最小示例中不再做太多事情的机器人类:

public class Robot {

    Coordinates future = new Coordinates(0, 0);

    public void think () {
        future.setY(1.0);
    }

    public Coordinates move() {
        return future;
    }
}

更新 刚刚安装了 JDK 8,现在异常更能说明问题:

Exception in thread "Thread-4" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-4

在网上搜索了一下,似乎我需要使用 Platform.runLater() 来避免这种情况发生。但我不知道该怎么做。 我将尝试再次解释我的项目现在的结构:

我有一个类 Visualizer,它扩展了 Application,它还包括 main() 方法,并将包含模拟区域的 UI 绘制为 GraphicsContext Canvas 上的灰色矩形.在 UI 完成后,它会创建一个实现 Runnable 的类 Simulator 线程。 Simulator 类中的 run() 方法进入了一个 while(true) 循环。只要没有单击任何按钮,循环只会执行 thread.sleep()。如果单击开始按钮,则会执行一些计算,并调用 Visualizer 上的一些方法,在模拟画布上绘制内容。据我了解,这些调用会导致异常,因为它们是由非 JavaFX 线程生成的。

我是否必须使用 runLater() 来避免这个问题,如果是,我应该在哪里以及如何做到这一点?

更新 2 最终让它稳定运行(据我现在所知),通过将所有更改 UI/Canvas 的公共方法的代码包装到 runLater 块中,例如

public static void drawStuff() {
    Platform.runLater(new Runnable() {
    @Override
        public void run() {
            // draw stuff
        }
    });
}

【问题讨论】:

  • 要缩小问题范围,请创建minimal example。如果这不能帮助您解决问题,请将最小示例粘贴到您的问题中。 (也可以试试Java 8,Java 8 中修复了各种此类错误)。另外我认为上网本和 PC 的错误是不同的,因为一个运行的是软件渲染器,另一个运行的是硬件加速渲染器。
  • jewelsea 是对的。我可以冒险对你说各种各样的事情,但仍然不是最终的解决方案,因为尽管已经理解了你的问题,但还是有必要观察你的程序代码。起初,这似乎是线程处理不当的问题。实际上,我几乎可以肯定它可以,但我还不能确定任何事情。
  • @Loa 您的假设可能是正确的,即它可能是对线程的不良处理,尽管它在某种程度上与白点坐标的 HashMap 有关被存储,因为只有在我实现这些问题时才开始出现问题。编辑:几乎可以肯定的是,在某处创建点/坐标对象实例而不稍后再次破坏它们是一个问题。在每次迭代中添加 System.gc() 调用似乎已经解决了问题,但感觉更像是创可贴而不是真正的解决方案。
  • @jewelsea 我的讲师要求我在明天之前创建一个完整的 javadoc 文档,所以我必须先这样做,然后我会看看是否可以运行一个最小的示例。也许这甚至会帮助我自己找到问题。 ;) 编辑:javadoc 文档也有帮助吗?
  • @Midnight 非常棒!我刚刚阅读了 JavaFX 文章中的并发,它明确指出 JavaFX 场景图不是线程安全的,建议使用 Platform.runLater();

标签: exception hashmap javafx runnable


【解决方案1】:

只是发布此内容,以便读者立即清楚该问题实际上有答案/解决方案。

我遇到了同样的问题,正如作者最后指出自己是“更新”。

如果您遇到此问题,您可能正在另一个线程中进行 ui 画布操作。您应该始终在应用程序线程上执行 ui 操作!使用

Platform.runLater(()->{
    //your code
});

【讨论】:

  • 而且顺便说一句,即使我使用的是 javafx 11,异常仍然没有告诉我们:“无法识别的 PGCanvas 令牌:68”
猜你喜欢
  • 1970-01-01
  • 2021-07-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-09
  • 2019-03-29
  • 2012-02-15
  • 2018-06-17
相关资源
最近更新 更多