1

I proceed to read and investigate concurrency in practice book and I cannot understand the example from chapter 9.3.2.

This chapter about GUI applications development.

In the text author says that if event handler is long running task - you can run it in separated thread to raise application responiveness.

and author provided following code in book:

BackgroundTask:

abstract class BackgroundTask<V> implements Runnable, Future<V> {
    private final FutureTask<V> computation = new Computation();

    private class Computation extends FutureTask<V> {
        public Computation() {
            super(new Callable<V>() {
                public V call() throws Exception {
                    return BackgroundTask.this.compute();
                }
            });
        }

        protected final void done() {
            GuiExecutor.instance().execute(new Runnable() {
                public void run() {
                    V value = null;
                    Throwable thrown = null;
                    boolean cancelled = false;
                    try {
                        value = get();
                    } catch (ExecutionException e) {
                        thrown = e.getCause();
                    } catch (CancellationException e) {
                        cancelled = true;
                    } catch (InterruptedException consumed) {
                    } finally {
                        onCompletion(value, thrown, cancelled);
                    }
                }

                ;
            });
        }
    }

    protected void setProgress(final int current, final int max) {
        GuiExecutor.instance().execute(new Runnable() {
            public void run() {
                onProgress(current, max);
            }
        });
    }

    // Called in the background thread
    protected abstract V compute() throws Exception;

    // Called in the event thread
    protected void onCompletion(V result, Throwable exception,
                                boolean cancelled) {
    }

    protected void onProgress(int current, int max) {
    }
    // Other Future methods forwarded to computation
}

and runInBackground method:

public void runInBackground(final Runnable task) {
        startButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                class CancelListener implements ActionListener {
                    BackgroundTask<?> task;

                    public void actionPerformed(ActionEvent event) {
                        if (task != null)
                            task.cancel(true);
                    }
                }
                final CancelListener listener = new CancelListener();
                listener.task = new BackgroundTask<Void>() {
                    public Void compute() {
                        while (moreWork() && !isCancelled())
                            doSomeWork();
                        return null;
                    }

                    public void onCompletion(boolean cancelled, String s,
                                             Throwable exception) {
                        cancelButton.removeActionListener(listener);
                        label.setText("done");
                    }
                };
                cancelButton.addActionListener(listener);
                backgroundExec.execute(task);
            }
        });
    } 

I cannot understand the logic of this code. According the code I see runInBackground method add listener to startButton which run task(which passed as argument of runInBackground method ) in separated(non Swing) thread.

But other code of this method unclear for me. According the book text we should have possibility to interrupt this task from Swing thread. But in method text we add additional listener to the cancelButton which makes work which cannot be related with stopping task which was passed as runInBackground method argument.

Please, clarify this thing.

gstackoverflow
  • 36,709
  • 117
  • 359
  • 710
  • 1
    See also [`SwingWorker`](https://docs.oracle.com/javase/8/docs/api/javax/swing/SwingWorker.html), which implements `Runnable`, `Future`, and `RunnableFuture`. – trashgod Mar 02 '17 at 20:52

2 Answers2

2

Swing is essentially single threaded. If a long running task is queued up on the Event Dispatch Thread (EDT), it will block until completion and prevent further event processing. If the EDT is blocked, this will make the GUI appear to be "frozen" (at least until the queue processing resumes).

To avoid this, it's a good practice to put non-GUI work onto other threads. Often, creation of these threads is triggered by a GUI event and the threads signal their completion via callback, so the GUI can be updated as needed.

In this example, the button press event on CancelButton does originate from the EDT. However the event handler for that button press must retain a reference to the BackgroundTask so it can invoke the cancel(true) method. So when the user clicks startButton there following sequence occurs:

  1. ActionListner for startButton is triggered by user pressing button.
  2. BackgroundTask is created.
  3. A new ActionLister with a reference to BackgroundTask is attached to cancelButton
  4. The BackgroundTask is started.

If the user clicks on the cancel button, the attached ActionLister will call cancel() on BackgroundTask.

Otherwise, once the BackgroundTask is complete, it removes the CancelListener since cancelling a completed task would be pointless.

For more on how the EDT works, you might want to check out this question: Java Event-Dispatching Thread explanation

EDIT As pointed out in the comments, Swing components are generally not thread safe and it is recommended they only be modified from the EDT.

At first glance, it appears the onCompletion() method of BackgroundTask is modifying both the cancelButton and label from a worker thread rather than the EDT.

However, the GuiExecutor provides an execute() method which ensures the Runnable passed in is run on the EDT:

public void execute(Runnable r) {
    if (SwingUtilities.isEventDispatchThread())
        r.run();
    else
        SwingUtilities.invokeLater(r);
}

BackgroundTask uses the execute() method to invoke its onCompletion() method. As a result, the modifications to cancelButton and label are being run from the EDT.

Since runInBackground it's also manipulating Swing components, it should be invoked from the EDT too.

Community
  • 1
  • 1
Jason Braucht
  • 2,358
  • 19
  • 31
  • Can we also discuss the fact that Swing is not thread safe and should not interact with the UI outside of the context of the EDT - cause that example is violating that rule – MadProgrammer Mar 02 '17 at 19:46
  • Good point @MadProgrammer - I've made an edit to try to address the thread safety concerns. – Jason Braucht Mar 02 '17 at 20:39
  • @Jason Braucht but input task of runInBackground mwthod unrelated with listener of cancel button – gstackoverflow Mar 04 '17 at 13:20
  • @Jason Braucht, please read http://stackoverflow.com/a/42596532/2674303 – gstackoverflow Mar 04 '17 at 13:22
  • @gstackoverflow - I *totally* misunderstood that your original question was asking why `task` was passed to `backgroundExec.execute()` rather than `listener.task`. With benefit of seeing the book errata, your original question (and the book's correction) make total sense. – Jason Braucht Mar 04 '17 at 13:33
1

This book has errata list

In Listing 9.8, the argument to backgroundExec.execute should be listener.task rather than just task, and the first and last lines of the listing should be deleted.

Thus my concern was right.

gstackoverflow
  • 36,709
  • 117
  • 359
  • 710