实践中并发的runInBackground方法的逻辑[9.3.2]

时间:2017-03-02 15:50:32

标签: java multithreading swing concurrency

我继续阅读并研究练习册中的并发性,我无法理解9.3.2节中的例子。

本章关于GUI应用程序开发。

在文中,作者说如果事件处理程序是长时间运行的任务 - 你可以在分离的线程中运行它来提高应用程序的响应能力。

并且作者在书中提供了以下代码:

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
}

runInBackground 方法:

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);
            }
        });
    } 

我无法理解这段代码的逻辑。根据代码,我看到runInBackground方法将侦听器添加到 startButton ,该侦听器在分离的(非Swing)线程中运行任务(以runInBackground方法的参数传递)。

但这种方法的其他代码对我来说不清楚。根据书中的文字,我们应该有可能从Swing线程中断这个任务。但是在方法文本中,我们向 cancelButton 添加了额外的监听器,这使得与停止作为runInBackground方法参数传递的任务无关的工作。

请澄清这件事。

2 个答案:

答案 0 :(得分:2)

Swing基本上是单线程的。如果长时间运行的任务在Event Dispatch Thread (EDT)上排队,它将阻塞直到完成并阻止进一步的事件处理。如果EDT被阻止,这将使GUI看起来“冻结”(至少在队列处理恢复之前)。

为避免这种情况,将非GUI工作放到其他线程上是一种很好的做法。通常,这些线程的创建由GUI事件触发,并且线程通过回调发出完成信号,因此可以根据需要更新GUI。

在此示例中,CancelButton 上的按钮按下事件来自EDT。但是,按下该按钮的事件处理程序必须保留对BackgroundTask的引用,以便它可以调用cancel(true)方法。因此,当用户点击startButton时,会出现以下序列:

    {li> ActionListner用于startButton由用户按下按钮触发。
  1. BackgroundTask已创建。
  2. 引用ActionLister的新BackgroundTask附加到cancelButton
  3. BackgroundTask已启动。
  4. 如果用户点击取消按钮,则附加的ActionLister会在cancel()上致电BackgroundTask

    否则,BackgroundTask完成后,会删除CancelListener,因为取消已完成的任务将毫无意义。

    有关EDT如何运作的更多信息,您可以查看以下问题:Java Event-Dispatching Thread explanation

    编辑 正如评论中所指出的,Swing组件通常不是线程安全的,它是recommended they only be modified from the EDT

    乍一看,似乎onCompletion() BackgroundTask方法正在修改工作线程中的cancelButtonlabel而不是EDT。

    但是,GuiExecutor提供了execute()方法,可确保传入的Runnable在EDT上运行:

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

    BackgroundTask使用execute()方法调用其onCompletion()方法。因此,对cancelButtonlabel的修改正在从EDT运行。

    由于runInBackground它也在操纵Swing组件,所以它也应该从EDT调用。

答案 1 :(得分:1)

这本书有errata列表

  

在代码清单9.8中,应该是backgroundExec.execute的参数   listener.task而不仅仅是task,以及第一行和最后一行   列表应该删除。

因此我的担心是正确的。