Java awt EventListener 异步?

Posted

技术标签:

【中文标题】Java awt EventListener 异步?【英文标题】:Java awt EventListener asynchronous? 【发布时间】:2017-04-11 15:58:39 【问题描述】:

我正在开发一个 Java awt 应用程序。我们目前有一个实现可运行并为我们的应用程序中的对象调用渲染的类。我们还有键盘和鼠标监听器,它们调用各种对象的函数。我注意到当多个键被快速按下时会发生一个奇怪的错误,经过一些调查后,似乎事件侦听器被异步调用,与正在执行渲染的主线程分开。任何人都可以确认 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


【问题讨论】:

你倒退了。它的 your 应用程序没有与 Swing 正确同步。 Swing 在它自己的线程上做所有事情(Event Disptach Thread = EDT)。在 EDT 之外做事是最常见的错误之一,会导致虚假且难以发现的错误。 哦,好的,我该如何解决这个问题? 取决于程序到底想做什么。您可以在 EDT 上运行整个逻辑(例如,使用 Swing 计时器,或严格事件驱动),或者如果您需要使用多个线程,您可以使用 SwingUtilities.invoke(Later) 在 GUI 中安排更新。不用说,如果没有某种 形式的同步,您无法 在线程之间共享数据。必须绝对避免渲染一个可能被另一个线程同时修改的模型对象。 好的,谢谢。我想我可以用 Swing 计时器解决这个问题。如果您想在答案中发表评论,我想我可以接受。 【参考方案1】:

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

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

因此,您附加到 Swing 组件的任何侦听器实际上都在 EDT 上运行。绘制,只要涉及到 Component 的 paint() 方法,也发生在 EDT 上。

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

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

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

对于不断更新 GUI 的游戏,上述方法是不够的。对于简单的游戏,一切都可以在 EDT 上运行,例如SwingTimer 获取常规“滴答”,其中 Swing 从 EDT 调用应用程序代码。以这种方式运行所有逻辑可以避免线程问题。

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

这只是您可以采取的方法的一个非常粗略的概述:)

【讨论】:

阅读 Concurrency 上的 Swing 教程,了解有关 EDT 的更多信息。

以上是关于Java awt EventListener 异步?的主要内容,如果未能解决你的问题,请参考以下文章

Java:允许在我的库存中进行丢弃操作?

Spring Boot | 事件监听器异步处理事件,实现代码解耦

关于java中的,Java.awt 和java.awt

[Javascript] Promise

AWT缓存组故障分析及恢复

java.awt.AWTException:java.awt.Robot.(Robot.java:94) 的无头环境 [重复]