1

以下代码在屏幕上滑动一张卡片。当我关闭主窗口时,我希望事件调度线程也会关闭,但事实并非如此。关于为什么 ScheduledExecutorService 线程阻止 EDT 关闭的任何想法?

import java.awt.Graphics;
import java.net.URL;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class Main extends JPanel
{
  private float x = 1;

  public void next()
  {
    x *= 1.1;
    System.out.println(x);
    repaint();
  }

  @Override
  protected void paintComponent(Graphics g)
  {
    super.paintComponent(g);
    URL url = getClass().getResource("/209px-Queen_of_diamonds_en.svg.png");
    g.drawImage(new ImageIcon(url).getImage(), (int) x, 50, null);
  }

  public static void main(String[] args)
  {
    JFrame frame = new JFrame();
    final Main main = new Main();
    frame.getContentPane().add(main);
    frame.setSize(800, 600);
    frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
    frame.setVisible(true);

    ScheduledExecutorService timer = Executors.newScheduledThreadPool(1, new ThreadFactory()
    {
      public Thread newThread(Runnable r)
      {
        Thread result = new Thread(r);
        result.setDaemon(true);
        return result;
      }
    });
    timer.scheduleAtFixedRate(new Runnable()
    {
      public void run()
      {
        SwingUtilities.invokeLater(new Runnable()
        {
          public void run()
          {
            main.next();
          }
        });
      }
    }, 100, 100, TimeUnit.MILLISECONDS);
  }
}
4

4 回答 4

3

关闭 a 时的默认行为JFrame只是隐藏它,而不是导致应用程序退出。您需要致电:

frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

ScheduledExecutorService换句话说:这与;无关。这与 Event Dispatch 线程不是守护线程这一事实有关。

额外的

而不是使用ScheduledExecutorServicewhich 反过来调用SwingUtilities.invoke...您应该考虑使用javax.swing.Timer,它将直接在 Event Dispatch 线程上ActionEvent定期触发 s ,从而使您的代码更简单/更紧凑,并消除对额外线程的需要。

此外,您正在重新创建ImageIcon每个动画帧,这将非常低效,特别是在紧凑的动画循环中。最好在应用程序启动时创建一次。

于 2010-02-09T17:41:31.273 回答
1

您的线程工厂是正确的。如果您EXIT_ON_CLOSE在框架上设置,那么它将退出。

但是,请考虑改用诸如Trident之类的库。

于 2010-02-09T19:15:30.000 回答
1

我在这篇出色的博客文章中找到了答案:http ://www.pushing-pixels.org/?p=369

在当前的实现中,当以下三个条件为真时,AWT 会终止其所有帮助线程,从而允许应用程序干净地退出:

  • 没有可显示的 AWT 或 Swing 组件。
  • 本机事件队列中没有本机事件。
  • java EventQueues 中没有 AWT 事件。

[...]

在当前实现中,此超时为 1000 毫秒(或一秒)。这实际上意味着 AWT 在处理应用程序中的最后一个窗口并处理所有未决事件后不会立即关闭。相反,它每秒唤醒一次,在睡眠期间检查任何未决或已处理的事件,如果有任何此类事件,则继续睡眠。

作者接着说,尽管相关的窗口不再可见,但他的代码每 100 毫秒向 EDT 发布一个事件。这正是我的情况!ScheduledExecutorService 将事件发布到 EDT,从而阻止 AWT 关闭,这反过来意味着 ScheduledExecutorService 将继续发布更多事件。

顺便说一句,我对推荐使用JFrame.EXIT_ON_CLOSE. 我猜每个人都有自己的想法,但我建议您阅读http://findbugs.sourceforge.net/bugDescriptions.html#DM_EXIT

于 2010-02-11T04:08:26.357 回答
1

我认为,与其在 中使用守护线程,ScheduledExecutorService不如在用户想要退出时明确关闭它。您可以通过将 WindowListener 添加到主框架来做到这一点:

  public static void main(String[] args)
  {
    JFrame frame = new JFrame();
    final Main main = new Main();
    frame.getContentPane().add(main);
    frame.setSize(800, 600);
    frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
    frame.setVisible(true);

    final ScheduledExecutorService timer = Executors.newScheduledThreadPool(1);
    timer.scheduleAtFixedRate(new Runnable()
    {
      public void run()
      {
        // NOTE that you don't need invokeLater here because repaint() is thread-safe
        main.next();
      }
    }, 100, 100, TimeUnit.MILLISECONDS);
  }
  // Listen to main frame closure and shut down timer
  main.addWindowListener(new WindowAdapter()
  {
      public void windowClosed(WindowEvent e)
      {
          timer.shutdownNow();
      }
  });
}

请注意我对您的代码段所做的更改:

  1. timer现在已声明final(需要,因为它被内部匿名类引用)
  2. 没有更多的ThreadFactory传递给newScheduledThreadPool
  3. 我已经删除了invokeLaterfor 调用 的使用,main.next()因为在那里进行的唯一 Swing 调用是repaint()少数几个线程安全的 Swing 方法之一。

请注意,我没有尝试过上面的代码,它应该可以编译,我认为它也应该可以解决您的问题。试试看,让我们知道!

于 2010-02-11T17:40:08.447 回答