1 Reply Latest reply: Jul 30, 2014 4:21 AM by TPD-Opitz RSS

    Need help with SwingWorker problem

    skiaddict1

      Greetings all,

       

      I am writing the front end to a database, for an accounting system.  Currently I am working on the Search window.  It contains some widgets to define what to search for, and a table for displaying the results.  The basic idea is that to avoid the need for a "Start" button, any time the user makes a change to a widget, if there is a search whose results are currently being put into the results table then that is halted and a new search process is started.

       

      I have implemented the search thread using SwingWorker and it all works well, that is except for the fact that the GUI is completely non-responsive until all the results from the current search are in the results table.  This is happening because my various widget listeners are not being told of state changes until my SwingWorker is completely finished!  This means that my code to call SwingWorker.cancel() is useless.

       

      Fortunately I have managed to replicate the same behaviour with a slightly amended version of one of the SwingWorker tutorial examples, Flipper:

      import java.awt.*;
      import java.awt.event.*;
      import java.text.SimpleDateFormat;
      import java.util.*;
      import java.util.List;
      import javax.swing.*;
      import javax.swing.border.Border;
      
      public class Flipper extends JFrame implements ActionListener {
        private final GridBagConstraints constraints;
        private final JTextField headsText, totalText, devText;
        private final Border border =  BorderFactory.createLoweredBevelBorder();
        private final JButton startButton, stopButton;
        private FlipTask flipTask;
      
      private JTextField makeText() {
          JTextField t = new JTextField(20);
          t.setEditable(false);
          t.setHorizontalAlignment(JTextField.RIGHT);
          t.setBorder(border);
          getContentPane().add(t, constraints);
          return t;
        }
      
        private JButton makeButton(String caption) {
          JButton b = new JButton(caption);
          b.setActionCommand(caption);
          b.addActionListener(this);
          getContentPane().add(b, constraints);
          return b;
        }
      
      public Flipper() {
          super("Flipper");
          setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
          getContentPane().setLayout(new GridBagLayout());
          constraints = new GridBagConstraints();
          constraints.insets = new Insets(3, 10, 3, 10);
          headsText = makeText();
          totalText = makeText();
          devText = makeText();
          startButton = makeButton("Start");
          stopButton = makeButton("Stop");
          stopButton.setEnabled(false);
          pack();
          setVisible(true);
        }
      
      private static class FlipPair {
          private final long heads, total;
        FlipPair(long heads, long total)   {
            this.heads = heads;
            this.total = total;
          }
        }
      
      private class FlipTask extends SwingWorker<Void, FlipPair> {
          protected Void doInBackground() {
            long heads = 0;
            long total = 0;
            Random random = new Random();
            while (! isCancelled())     {
              total++;
              if (random.nextBoolean()) {
                heads++;
              }
              publish(new FlipPair(heads, total));
            }
            return null;
          }
      
          protected void process(List<FlipPair> pairs) {
            if (!isCancelled()) {
              // wait a bit
              ArrayList<Integer> is = new ArrayList<Integer>();
              for (int j = 0; j < 2500000; j++) {
                is.add(j);
              }
              printTimedMessage(" -- process()");
      
              FlipPair pair = pairs.get(pairs.size() - 1);
              headsText.setText(String.format("%d", pair.heads));
              totalText.setText(String.format("%d", pair.total));
              devText.setText(String.format("%.10g", ((double) pair.heads) / ((double) pair.total) - 0.5));
            }
          }
        }
      
        public void actionPerformed(ActionEvent e) {
          if ("Start" == e.getActionCommand()) {
            startButton.setEnabled(false);
            stopButton.setEnabled(true);
            (flipTask = new FlipTask()).execute();
          }
          else if ("Stop" == e.getActionCommand()) {
            printTimedMessage(" -- stop button pressed");
            startButton.setEnabled(true);
            stopButton.setEnabled(false);
            flipTask.cancel(true);
            flipTask = null;
          }
        }
      
        void printTimedMessage(String message) {
          GregorianCalendar g = new GregorianCalendar();
          g.setTimeInMillis(System.currentTimeMillis());
          System.out.println(
            new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS").format(g.getTime()) +
            message);
        }
      
        public static void main(String[] args) {
          SwingUtilities.invokeLater(new Runnable() {
            public void run() {
              new Flipper();
            }
          });
        }
      }
      
      

       

      (The added code is the busy loop at the top of process().)

       

      When I run the above, I wait for a couple of "process()" output statements and then I press on the Stop button.  Invariably one or more "process()" statements are output after that, and then finally the "stop button" one is output, and processing stops.  Sometimes as many as 3 "process()" statements are output before the "stop" one is.

       

      What on earth is going on here?  I thought the whole reason for having a SwingWorker was so that the GUI could remain responsive.  This is not at all what I'm seeing.  Why is the EDT not telling my widget listeners about GUI state changes until the SwingWorker has finished its work?  This is making for a very non-responsive GUI.  (With my slowed-down production version, using the same busy loop as above, it actually takes minutes!)

       

      What I want is for the users to be able to type a key, or choose something with the mouse, and for any and all processing of the current search to halt pretty much straight away.  How on earth can I achieve this?

       

      Many thanks!