Java awt EventListener 是异步的吗?

Java awt EventListener asynchronous?

我正在开发 Java awt 应用程序。我们目前有一个 class 实现 runnable 并为我们应用程序中的对象调用渲染。我们还有键盘和鼠标侦听器,可以调用各种对象上的函数。我注意到当快速按下多个键时会出现一个奇怪的错误,经过一些调查后,事件侦听器似乎与正在执行渲染的主线程分开异步调用。谁能确认 Java awt 事件侦听器是异步调用的,并提出可能的解决方案?

public class Driver extends Canvas implements Runnable
{
    private boolean running = false;
    private Integer frames;
    private Thread thread;

    private Window window;
    private Mouse mouse;
    private Keyboard keyboard;

    /**
     *  Constructor of Driver
     *  Initiates Window, Mouse, Keyboard, handlers
     */
    public Driver()
    {
        window = new Window("Dominion", this);
        mouse = new Mouse(this);
        keyboard = new Keyboard(this);
    }

    /**
     *  updates classes/variables that change per frame
     */
    public void tick() {
        //Framerate independent calls
    }


    /**
     *  Draws the game onto the window
     *  Calls other handler render to draw their parts
     */
    public void render()
    {
        BufferStrategy bs = this.getBufferStrategy();
        if (bs == null)
        {
            this.createBufferStrategy(2);
            return;
        }
        Graphics g = bs.getDrawGraphics();
        //Start Graphics

        g.setColor(Color.WHITE);
        g.fillRect(0, 0, window.getWidth(), window.getHeight());

//Rest of rendering

        //End Graphics
        g.dispose();
        bs.show();
    }

    /**
     *  Starts thread
     */
    public synchronized void start() {
        thread = new Thread(this);
        thread.start();
        running = true;
    }

    /**
     *  Stops thread
     */
    public synchronized void stop() {
        try {
            thread.join();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     *  Important game function that calls the render and tick methods
     */
    public void run() {
        this.requestFocus();
        long lastTime = System.nanoTime();
        double amountOfTicks = 60.0;
        double ns = 1000000000 / amountOfTicks;
        double delta = 0;
        long timer = System.currentTimeMillis();
        frames = 0;
        while (running) {
            long now = System.nanoTime();
            delta += (now - lastTime) / ns;
            lastTime = now;
            while (delta >= 1) {
                tick();
                delta--;
            }
            if (running)
                render();

            frames++;

            if (System.currentTimeMillis() - timer > 1000) {
                timer += 1000;
                System.out.println("FPS: " + frames);
                frames = 0;
            }
        }
        stop();
    }

    /**
     * getter for window
     * @return window
     */
    public Window getWindow()
    {
        return window;
    }

    /**
     *  Starts up the whole Client side of things
     */
    public static void main(String[] args)
    {
        new Driver();
    }
}

和鼠标

public class Mouse implements MouseListener, MouseMotionListener, MouseWheelListener{
    private Driver d;

    /**
     * Creates a Mouse object
     * @param driver
     */
    public Mouse(Driver driver) {
        this.d = driver;
        d.addMouseListener(this);
        d.addMouseMotionListener(this);
        d.addMouseWheelListener(this);
    }

    /**
     * Invoked when the mouse button has been clicked (pressed
     * and released) on a component.
     * @param e
     */
    @Override
    public void mouseClicked(MouseEvent e) {

    }

    //Other methods cut out

}

Swing 是严格的单线程,Swing 所做的一切都发生在它自己的线程(事件调度线程,又名 EDT)上。这不是您的 java 程序启动的主线程。很少有 Swing 方法可以从 EDT 之外安全地调用(遗憾的是 java 文档对此不是很明确)。

大多数 java GUI 框架具有相同的限制(例如 JavaFX 使用非常相似的方法)。

因此,您附加到 Swing 组件的任何侦听器实际上都会在 EDT 上执行 运行。就 Component 的 paint() 方法而言,绘画也发生在 EDT 上。

根据应用程序的要求,有许多方法可以在满足 Swings 线程要求的同时满足应用程序要求。

最简单的情况是完全由用户输入驱动的应用程序(在这种情况下,您只需创建一个 GUI,一切都会响应用户引起的事件,例如鼠标点击或按键)。由于侦听器中的所有代码 运行,它由 EDT 的 Swing 自动调用,不会发生线程问题。缺点是您不能执行长 运行ning 方法,因为它们会阻止 EDT,导致 GUI 无响应。

为了解决无响应的 GUI 问题,长时间的 运行ning 工作被安排在 EDT 之外,例如使用 SwingWorker 实用程序 class。这种方法适用于数据库访问、文件 I/O 或响应用户输入而完成的一般计算。

对于持续更新 GUI 的游戏,上述方法是不够的。对于简单的游戏,EDT 上的一切都可以 运行,例如SwingTimer 获取常规 "ticks",其中 Swing 从 EDT 调用应用程序代码。 运行 这样的所有逻辑都避免了线程问题。

对于还需要处理异步任务的成熟游戏,例如网络、资源流等,多线程方法更适合。这使事情变得更加复杂,因为应用程序需要确保数据对象是从例如GUI 的加载程序线程已正确同步,并且一次不会被多个线程修改。游戏状态也是如此,当游戏状态被渲染时,它不能被修改。如果游戏主线程和渲染需要异步(例如实时游戏逻辑如果渲染慢就不能等待),主游戏线程可能需要制作快照副本以传递游戏状态进行渲染。

这只是您可以采取的方式的粗略概述:)