views:

646

answers:

5

Hi,

I have a 2 processes to perform in my swing application, one to fill a list, and one to do operations on each element on the list. I've just moved the 2 processes into Swingworker threads to stop the GUI locking up while the tasks are performed, and because I will need to do this set of operations to several lists, so concurrency wouldn't be a bad idea in the first place. However, when I just ran

fillList.execute();
doStuffToList.execute();

the doStuffToList thread to ran on the empty list (duh...). How do I tell the second process to wait until the first one is done? I suppose I could just nest the second process at the end of the first one, but i dunno, it seems like bad practice.

+1  A: 

This may be what I'm looking for, but I'd like other answers too

http://www.indijava.in/community/tutorial/Using-SwingWorker-A-Step-by-Step-tutorial

Simonw
+1  A: 

Something like this would do it, I think?

boolean listIsFull=false;
class FillListWorker extends SwingWorker<Foo,Bar>
{
    ...
    protected void done()
    {
        synchronized (listYouveBeenFilling)
        {
            listIsFull=true;
            listYouveBeenFilling.notifyAll();
        }
    }
    ...
}

class DoStuffToListListWorker extends SwingWorker<Foo,Bar>
{
    ...
    protected Foo doInBackground()
    {
        synchronized (listYouveBeenFilling)
        {
            while (!listIsFull)
            {
                try
                {
                    listYouveBeenFilling.wait();
                }
                catch (InterruptedException ie)
                {
                    // Don't worry, we'll just wait again
                }
            }
        }
    }
    ...
}
Jon Bright
A: 

To perform two processes sequentially, traditionally you just call one method after the other(!).

fillList();
doStuffToList();

Or perhaps something like:

doStuffToList(fillList());

If you are processing one at a time, you might want two threads with a BlockingQueue between. You might go further by having multiple do-stuff threads.

As far as the AWT Event Dispatch Thread (EDT) is concerned it's just spun off an action without blocking, and will get notified later.

Tom Hawtin - tackline
+1  A: 

How do I tell the second process to wait until the first one is done? I suppose I could just nest the second process at the end of the first one, but i dunno, it seems like bad practice.

Have you looked into using callables & futures instead? They sound like a good match for this sort of thing (letting the doStuffToList work on a Future.get() instead of the actual list, so it'll be ready when get is called), apart from the whole swingworker business.. (Consider this a suggestion rather than an answer)

Tim
This could be exactly what I want, I'm looking into it now, thanks
Simonw
A: 

We have something like this:

private SwingWorkerExecutor swingWorkerExecutor;

//...

protected void runChain(List<SwingWorker<Void>> chainWorkers,
                        final SwingWorkerExecutor.RunAfter<Void> runAfter,
                        final SwingWorkerExecutor.RunOnError runOnError)
{
    final List<SwingWorker<Void>> remainingWorkers =
        chainWorkers.subList(1, chainWorkers.size());
    SwingWorkerExecutor.RunAfter<Void> chainRunAfter;
    if (chainWorkers.size() > 1)
    {
        chainRunAfter = new SwingWorkerExecutor.RunAfter<Void>()
        {
            @Override
            public void run(Void value)
            {
                runChain(remainingWorkers, runAfter, runOnError);
            }
        };
    }
    else
    {
        chainRunAfter = runAfter;
    }

    currentWorker = chainWorkers.get(0);

    swingWorkerExecutor.execute(currentWorker, chainRunAfter, runOnError);
}

This is pretty simple, IMO, because in our case the SwingWorkerExecutor actually contains all the hard to understand stuff:

public class DefaultSwingWorkerExecutor implements SwingWorkerExecutor
{
    @Override
    public <T> void execute(SwingWorker<T, ?> worker, RunAfter<T> after,
                            RunOnError onError)
    {
        worker.addPropertyChangeListener(
            new RunAfterHandler<T>(worker, after, onError));
        worker.execute();
    }

    private static class RunAfterHandler<T> implements PropertyChangeListener
    {
        private final SwingWorker<T, ?> worker;
        private final RunAfter<T> after;
        private final RunAfter<Throwable> onError;

        protected RunAfterHandler(SwingWorker<T, ?> worker, RunAfter<T> after,
                                  RunOnError onError)
        {
            this.worker = worker;
            this.after = after;
            this.onError = onError;
        }

        @Override
        public void propertyChange(PropertyChangeEvent evt)
        {
            if ("state".equals(evt.getPropertyName()) &&
                evt.getNewValue() == SwingWorker.StateValue.DONE)
            {
                if (worker.isCancelled())
                {
                    return;
                }

                try
                {
                    after.run(worker.get());
                }
                catch (InterruptedException e)
                {
                    Thread.currentThread().interrupt();
                }
                catch (ExecutionException e)
                {
                    onError.run(e);
                }
            }
        }
    }
}

There are some missing interfaces which should be pretty straight-forward to write without seeing them here.

Our real deployment SwingWorkerExecutor executes using an injected ExecutorService instead of the default one (this reduces the number of thread pools we need for a single app.) But the real reason we introduced SwingWorkerExecutor was that it simplifies and standardises the handling of SwingWorker success and error conditions and also allows replacing the logic for unit tests (which, as I'm sure you know, are a lot simpler if they are single threaded.) As you can see there is a bunch of boilerplate you would normally need for every single SwingWorker inside done(), so instead of doing that, we move the done() work into a callback.

The side-benefit is that things like running multiple Swing workers in a chain become pretty easy to implement.

Trejkaz