0

Hello I have been trying to learn on how to use SwingWorker to update the GUI of swing apps.

My question is this: if I have 2 swingworkers that can run concurrently (each one is fired from a different button) how to make sure that their done method is called in the EDT (to update the gui) in the same order they were fired?

My observations and ideas: I have tried to synchronize their doInBackground() methods to a lock to make sure that one has to wait for the other to finish doing their task before proceeding. The problem is that I have been able (just a few times) to see swingworker_1 finishing the doInBackground() (while the 2nd worker is waiting in the synchronized block), but the done method of the swingworker_2 was able to be called by the EDT before swingworker_1's done method even though swingworker_2 finished after. I thought about using publish just before the doInBackground() returns, this seems to always work, but might be a bad practice as it seems to be used for intermediate results and not the final results? Another possibility would be to use SwingUtilities.invokeLater inside the doInBackground() to update the gui before returning, again I am afraid it might be a bad practice as well. Thanks in advance.

The test I used, pressing the first button and then immediately the second one:

    //Code that runs in the first button
    SwingWorker<Void, Void> swingworker = new SwingWorker<Void, Void>() {

        @Override
        protected Void doInBackground() throws Exception {
            synchronized (lock) {
                System.out.println("Thread 1");

                Thread.sleep(1000);
                return null;
            }

        }

        @Override
        protected void done() {

            System.out.println("Done with thread 1 ");
            //update GUI    
        }

    };
    swingworker.execute();



    //Code that runs in the second button
    SwingWorker<Void, Void> swingworker = new SwingWorker<Void, Void>() {

        @Override
        protected Void doInBackground() throws Exception {
            synchronized (lock) {
                System.out.println("Thread 2");

                return null;
            }

        }

        @Override
        protected void done() {
            System.out.println("Done with thread 2 ");
            //update GUI
        }

    };
    swingworker.execute();

UPDATE: Just to clarify I am not concerned with who starts first (swingworker_1 or swingworker_2) that just depends on which button would be pressed first. What I want to guarantee is that if a worker from a button was the first to execute (and thus finish as the workers are synchronized in the doInBackground() method) then it should also be the first to enqueue to update the gui in the EDT. What I have found is that this does not necessarily happen even if the workers are synchronized sometimes the worker that starts the task later still manages to update the gui before the first one.

Cluster
  • 11
  • 5
  • 1
    I wonder if you are describing an XY problem and would request that you please tell more: For one, why is this order so important? What are these workers doing and what is your program doing? – Hovercraft Full Of Eels Oct 05 '16 at 16:19
  • 1
    Myself, I try to avoid using the `done()` method much, and instead usually listen for state changes to the workers using PropertyChangeListeners. This relieves the SwingWorker the responsibility of having to make any GUI-specific calls, or even have knowledge about the GUI or any of the calling code. – Hovercraft Full Of Eels Oct 05 '16 at 16:21
  • 1
    I don't see anything in your code that ensures that the first worker will acquire `lock`'s monitor before the second one does. – John Bollinger Oct 05 '16 at 16:24
  • Hello Hovercraft, the idea was to use the first swingworker/button to start the background data and when finished it would update a label or something to "Online". The second button could be used for shutdown and it would be responsible for termination of all the background threads, upon done it would update the Label to Offline. – Cluster Oct 05 '16 at 16:31
  • Hello John, the idea is not to make sure one runs before the other but that when one swingworker does run it updates the gui before the other, so if the second button is pressed before it should update the gui before button 1 and vice versa. – Cluster Oct 05 '16 at 16:32
  • Do you want to completely prevent the second worker from firing until the first one has completed its task? If so disable the 2nd button until it is needed. – Hovercraft Full Of Eels Oct 05 '16 at 16:32
  • Hello Hover, the disable button seems to be a nice idea, I think it could work, however would the ProperTyChangeListener you described also work in this example, as in make sure the worker that finished first would be able to update the gui first? – Cluster Oct 05 '16 at 16:37
  • Like [this](http://stackoverflow.com/a/11372932/230513)? – trashgod Oct 05 '16 at 17:59
  • Hello trashgod, your example is a nice one, but I think the use case is different than what I was looking for. I intend to be able to run each button and its swingworker in any possible way and concurrently and update the gui in the same order that a user clicked. So if I press the second button first, its swingworker finishes first and I wanted it to update the gui first. If I do disable the other button it would solve the problems but I am trying to figure out if there is a a way to guarantee updates are done in the same order without having to block the edt or disable button interaction. – Cluster Oct 05 '16 at 20:47

1 Answers1

0

If you are OK with the workers running serially, which they will if you synchronize on a lock per your sample code, and you are not using any of the other features of SwingWorker other than doInBackground() and done(), it may be easier for you to use a single thread executor and SwingUtilities.invokeLater(...) to present the results on the GUI.

This way, you are guaranteed that things submitted to the executor will be run in the order they are submitted.

If this works for you, you could try something like this:

final ExecutorService executor = Executors.newSingleThreadExecutor();

// Code that runs in the first button
Runnable worker1 = new Runnable() {
  @Override
  public void run() {
    System.out.println("Thread 1");
    try {
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      e.printStackTrace(System.err);
    }
    SwingUtilities.invokeLater(this::updateGUI);
  }

  private void updateGUI() {
    System.out.println("Done with thread 1");
    //update GUI
  }
};
executor.execute(worker1);

// Code that runs in the second button
Runnable worker2 = new Runnable() {
  @Override
  public void run() {
    System.out.println("Thread 2");
    SwingUtilities.invokeLater(this::updateGUI);
  }

  private void updateGUI() {
    System.out.println("Done with thread 2");
    //update GUI
  }
};
executor.execute(worker2);
clstrfsck
  • 14,715
  • 4
  • 44
  • 59
  • Hello msandiford, this would certainly work as like you said the workers execute one after the other and they enqueue to update the gui before returning. To adapt this I would execute worker1 in button1 and worker2 in button2. As I have said in my question initially I am convinced that using invokelater inside the doInBackground() of the swingworkers would work as well and I think publish would do the trick as well, just that they might be considered bad practice, each for a different reason? It just seems a weird design that the done won't necessarilly queue in the same order workers finish. – Cluster Oct 06 '16 at 08:41
  • You could use a `synchronized` block in `doInBackground`, and call `invokeLater` inside the synchronized block, but ultimately `SwingWorker` uses a multi-threaded executor, so there is no real guarantee as to which worker will run first for SwingWorkers submitted at nearly the same time. Given that worker submission is based on clicking GUI buttons this may not really matter for your use case. – clstrfsck Oct 06 '16 at 09:39
  • Exactly msandiford, it does not matter who runs first anyway, just that the one that runs first will enqueue for an update to the gui first. It seems invokelater, be it your approach (which is a nice and clever away by the way) or inside a swingworker synchronized doInBackground() , would ensure this order is met. The done method on the other hand can't guarantee this. The propertyChangeListener suggested before might also be an elegant solution, but I am afraid it might also not guarantee the proper order as there might be a delay between finishing doInBackground() and change to done status. – Cluster Oct 06 '16 at 10:11