首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >runInBackground方法在实践中的并发逻辑[9.3.2]

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

Stack Overflow用户
提问于 2017-03-02 15:50:32
回答 2查看 140关注 0票数 1

我继续阅读并研究实践书中的并发性,我无法理解第9.3.2章中的例子。

本章介绍GUI应用程序的开发。

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

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

BackgroundTask:

代码语言:javascript
运行
复制
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方法:

代码语言:javascript
运行
复制
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方法参数传递的停止任务无关。

请澄清这件事。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2017-03-04 13:22:40

这本书有errata列表

在清单9.8中,backgroundExec.execute的参数应该是listener.task,而不仅仅是任务,清单的第一行和最后一行应该被删除。

因此,我的关切是对的。

票数 1
EN

Stack Overflow用户

发布于 2017-03-02 17:44:23

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

为了避免这种情况,将非GUI工作放在其他线程上是一个很好的实践。通常,这些线程的创建是由GUI事件触发的,线程通过回调来表示它们的完成,因此可以根据需要更新GUI。

在本例中,CancelButton上的按钮按下事件确实起源于EDT。但是,按钮按下的事件处理程序必须保留对BackgroundTask的引用,以便它能够调用cancel(true)方法。因此,当用户单击startButton时,会出现以下顺序:

  1. ActionListner for startButton是由用户按下按钮触发的。
  2. 创建了BackgroundTask
  3. 一个与BackgroundTask相关的新的BackgroundTask附加到cancelButton
  4. BackgroundTask已经启动。

如果用户单击cancel按钮,附加的ActionLister将调用BackgroundTask上的cancel()

否则,一旦BackgroundTask完成,它就会删除CancelListener,因为取消已完成的任务是没有意义的。

有关EDT如何工作的更多信息,您可能需要查看以下问题:Java事件-分派线程解释

正如注释中指出的那样,编辑,Swing组件通常不是线程安全的,它是从EDT修改

乍一看,onCompletion()BackgroundTask方法似乎是从工作线程而不是从EDT修改cancelButtonlabel

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

代码语言:javascript
运行
复制
public void execute(Runnable r) {
    if (SwingUtilities.isEventDispatchThread())
        r.run();
    else
        SwingUtilities.invokeLater(r);
}

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

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

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/42559937

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档