Search code examples
javamultithreadingswingpublishswingworker

SwingWorker publish()/process() acts like done()


Implementation details: I'm working on a school project in which I have to simulate some queues. At random intervals, clients should be generated, the client selects one queue(i can have multiple queues) to enter, and is added to that queue data structure. Each queue has its own operator which removes clients form the queue it's attached to.

The problem: The client generator is run in a separate thread. The queue graphical representation is that of a ArrayList of JButtons, displayed on a GridLayout panel, with only 1 column. When I try to add a client(a JButton) to the panel, I want to use SwingWorker's publish() to publish a new JButton, to be added to the list. However, after a lot of headaches, and System.out.println's to figure out what's going one, I observed that the System.out.println() in the process() method is called only after the doBackground() method has finished.

Code Here:

  //run method of the ClientGenerator thread
  public void run()
  {

      System.out.println("Into thread Generator");
      SwingWorker<Void,JButton> worker=new SwingWorker<Void, JButton>()
      {
          int sleepTime;
          @Override
          protected Void doInBackground() throws Exception
          {

              while(checkTime())
              {
                  try
                  {
                      sleepTime=minInterval+r.nextInt(maxInterval - minInterval);
                      System.out.println("Sleeping - "+sleepTime+" milis");
                      Thread.sleep(sleepTime);
                      System.out.println("Woke up,"+sleepTime+" milis elapsed");

                  } catch (InterruptedException e)
                  {
                      e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
                  }
                  System.out.println("Generating client...");
                  newClient=new Client(clientMinService,clientMaxService,log);
                  System.out.println("Locking lock...");
                  operationsOnTheQueueLock.lock();
                  selectedQueueOperator=selectQueueOperator();
                  System.out.println("Adding new client to queue...");
                  selectedQueueOperator.getQueue().enqueue(newClient);
                  System.out.println("Publishing new JButton...");
                  publish(new JButton("C"+selectedQueueOperator.getClientIndicator()));
                  //}
                  // else
                  //  {
                  //     queueHolder.add(selectedQueueOperator.getQueueClients().get(0);
                  // }

                  System.out.println("Unlocking lock...");
                  operationsOnTheQueueLock.unlock();
                  System.out.println("Lock unlocked! Should enter while again and sleep");

              }
          return null;
          }
          @Override
          public void process(List<JButton> chunks)
          {


                  newClientButton=chunks.get(chunks.size()-1);

                  System.out.println("Process runs.Jbutton index="+newClientButton.getText());
                  newClientButton.setFont(new Font("Arial", Font.PLAIN, 10));
                  newClientButton.setBackground(Color.lightGray);
                  newClientButton.setVisible(true);
                  newClientButton.setEnabled(false);
                  clients=selectedQueueOperator.getQueueClients();
                  clients.add(newClientButton);
                  selectedQueueOperator.setQueueClients(clients);
                  //       if(selectedQueueOperator.getQueueClients().size()>0)
              //   {

                  queueHolder=selectedQueueOperator.getQueueHolder();
                  queueHolder.add(clients.get(clients.size()-1));
                  selectedQueueOperator.setQueueHolder(queueHolder);
          }


           //   return null;  //To change body of implemented methods use File | Settings | File Templates.
      };
      worker.execute();
      try {
          worker.get();
      } catch (InterruptedException e) {
          e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
      } catch (ExecutionException e) {
          e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
      }
  }

output:

Sleeping - 1260 milis
Woke up,1260 milis elapsed
Generating client...
Locking lock...
Adding new client to queue...
Publishing new JButton... ///here I should see "Process runs.Jbutton index=C0"
Unlocking lock...
Lock unlocked! Should enter while again and sleep
Sleeping - 1901 milis
Woke up,1901 milis elapsed
Generating client...
Locking lock...
Adding new client to queue...
Publishing new JButton...///here I should see "Process runs.Jbutton index=C1
Unlocking lock...
Lock unlocked! Should enter while again and sleep
Process runs.Jbutton index=C0  //instead, Process runs only in the end.

This is just a basic example, for 2 iterations. Clients should be generated only from time to time, so at the beginning I sleep the thread for a certain amount of time. Then I generate the client object, then I want to generate and add the button to my JPanel component, in the process() method.

This last part, obviously isn't happening. Any ideeas why? I'm out of things to try, regarding SwingWorker...

Thanks in advance!

Later edit: "lock" is defined as:

Lock lock = new ReentrantLock();

and is passed as a parameter from the class that managed my ClientsGenerator(this) class, and my class which removes clients from the queue. It's used to synchronize the two, when performing operations over the ArrayList& display.


Solution

  • The whole point of threads is that things are not executed in sequence. doInBackground() can finish (a while loop iteration) before process() is called. doInBackground() runs on the swing worker thread process() runs on the EDT.

    process() will run before done() (because it also runs on the EDT).

    As noted by the other answer: you should only publish the text, then create the JButton in process().

    Note normally you start a SwingWorker from the EDT, in that case you should not call get() on the EDT (which would block it).

    Simple example:

    import java.awt.*;
    import java.awt.event.ActionEvent;
    import java.util.*;
    import javax.swing.*;
    
    public class SwingWorkerTest {
        public static void main(String[] args) {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    final JPanel panel = new JPanel(new GridLayout(0, 1));
                    new SwingWorker<Void, String>() {
                        @Override
                        protected Void doInBackground() throws Exception {
                            Random random = new Random();
                            int count = 1;
                            while (count < 100) {
                                publish("Button " + (count++));
                                Thread.sleep(random.nextInt(1000) + 500);
                            }
                            return null;
                        }
    
                        @Override
                        protected void process(List<String> chunks) {
                            for (String text : chunks) {
                                panel.add(new JButton(new AbstractAction(text) {
                                    @Override
                                    public void actionPerformed(ActionEvent e) {
                                        panel.remove((JButton) e.getSource());
                                        panel.revalidate();
                                        panel.repaint();
                                    }
                                }));
                            }
                            panel.revalidate();
                            panel.repaint();
                        }
                    }.execute();
    
                    JFrame frame = new JFrame("Test");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.getContentPane().add(new JScrollPane(panel));
                    frame.setPreferredSize(new Dimension(400, 300));
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                }
            });
        }
    }