views:

167

answers:

4

The following code slides a card across the screen. When I shut down the main window, I expect the event dispatch thread to shut down as well, but it does not. Any ideas on why the ScheduledExecutorService thread prevents the EDT from shutting down?

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);
  }
}
+3  A: 

The default behaviour when you close a JFrame is simply to hide it, not to cause the application to exit. You need to call:

frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

In other words: This has nothing to do with the ScheduledExecutorService; It is to do with the fact that the Event Dispatch thread is not a daemon thread.

ADDITIONAL

Rather than use a ScheduledExecutorService which in turn calls SwingUtilities.invoke... you should consider using javax.swing.Timer, which will fire ActionEvents periodically directly on the Event Dispatch thread, hence making your code simpler / more compact and removing the need for the additional thread.

Also, you are recreating the ImageIcon on every animation frame which will be very inefficient, particularly in a tight animation loop. Far better to create it once when the application starts.

Adamski
I appreciate the code improvement tips, but I believe your answer is wrong. My code already uses frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE). Why would the frame prevent the EDT from shutting down?
Gili
DISPOSE_ON_CLOSE will not cause the application to exit, only EXIT_ON_CLOSE will do this - Have you checked the Javadoc description of what these constants do? Once the EDT is running (i.e. with any Swing app) you cannot simply return from main(...) - The non-daemon EDT thread will continue to run and keep your app. alive; i.e. It's not the JFrame specifically that's keeping the app. alive.
Adamski
Adamski, I believe you are mistaken about DISPOSE_ON_CLOSE. Please read the answer I just posted.
Gili
+1  A: 

Your thread factory is correct. If you set EXIT_ON_CLOSE on the frame then it will exit.

However, consider using a library such as Trident instead.

finnw
A: 

I ran across the answer in this excellent blog post: http://www.pushing-pixels.org/?p=369

With the current implementation, AWT terminates all its helper threads allowing the application to exit cleanly when the following three conditions are true:

  • There are no displayable AWT or Swing components.
  • There are no native events in the native event queue.
  • There are no AWT events in java EventQueues.

[...]

In the current implementation this timeout is 1000 ms (or one second). What this effectively means that AWT is not shutdown immediately after disposing the last window in your application and processing all pending events. Instead, it wakes every second, checks for any pending or processed events during the sleep and continues sleeping if there have been any such events.

The author goes on to say that his code posts an event to the EDT every 100ms in spite of the fact that the associated Window is no longer visible. This is exactly what happens in my case as well! The ScheduledExecutorService is posting events into the EDT, which in turn prevents AWT from shutting down, which in turn means that the ScheduledExecutorService will keep on posting more events.

As an aside, I am surprised by the number of people that recommend the use of JFrame.EXIT_ON_CLOSE. Each to his own I guess, but I recommend you read http://findbugs.sourceforge.net/bugDescriptions.html#DM_EXIT

Gili
+1  A: 

I think that, rather than using daemon threads in your ScheduledExecutorService, you'd better explicitly shut it down when the user wants to quit. You can do that by adding a WindowListener to the main frame:

  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();
      }
  });
}

Note the changes I've made to your snippet:

  1. timer is now declared final (needed as it is referenced by an inner anonymous class)
  2. There is no more ThreadFactory passed to newScheduledThreadPool
  3. I have removed the use of invokeLater for calling main.next() because the only Swing call made there is repaint() which is one of the few Swing methods that are thread-safe.

Please note that I haven't tried the code above, it should compile and I think it should also solve your problem. Try it and let us know!

jfpoilpret
@jfpoilpret, you provided a good answer but my original question was misleading. I was more interested in why the EDT wasn't shutting down then how to fix it. I up-voted your answer. Sorry for the confusion.
Gili
Actually I don't think the problem is due to the EDT not shutting down, but I believe ScheduledExecutorService may have its own worker thread(s), not created by ThreadFactory.newThread.
jfpoilpret
Also be aware the the rule that was given in the other answer doesn't work when the application is launched from Java WebStart (there was a bug reported for this) and requires an explicit System.exit(n).That may be the reason why some people have advised the use of JFrame.EXIT_ON_CLOSE.
jfpoilpret
If you want to know, in your original example, why the application doesn't exit after closing the window, then you should take a look at which threads are still running at that time.You can take a look at http://www.crazysquirrel.com/computing/java/basics/java-thread-dump.jspx to see how to do.You'll also have to modify your ThreadFactory to give explicit names to your own Threads.
jfpoilpret