【问题标题】:Why is publish necessary for SwingWorkers?为什么 SwingWorkers 需要发布?
【发布时间】:2016-02-25 19:25:55
【问题描述】:

我正在尝试在后台运行一个进程三秒钟,并让它可以取消。现在我有一个例子,它增加一个 int 直到三秒过去或工人被取消。

import java.util.List;

import javax.swing.SwingWorker;

public class Example {

    public static void main(String[] args) throws InterruptedException {
        ExampleWorker worker = new ExampleWorker();
        worker.execute();
        Thread.sleep(10);
        //worker.cancel(false);
    }

}

class ExampleWorker extends SwingWorker<String, Integer> {

    private int n = 0;
    private long startTime;
    private boolean cancelled = false;

    @Override
    protected String doInBackground() throws Exception {
        System.out.println("doInBackground()");
        startTime = System.currentTimeMillis();
        while (!cancelled && System.currentTimeMillis() - startTime < 3000) {
            publish(n++);
        }
        return "What is this used for?";
    }

    @Override
    protected void done() {
        cancelled = true;
        System.out.println(n);
    }

}

我曾经有一个打印出n的处理方法。我删除了它,但我保留了publish(n++)。一切正常。但是当我用n++ 替换publish(n++) 时,如果我不取消工人,什么都不会打印出来。为什么删除发布会产生这个问题?

【问题讨论】:

  • 线程安全漏洞阻止工作人员看到变量变化,使变量原子化
  • 你的处理方法在哪里? Publish 会将传递给进程的项目排队供您处理,在 EDT 上调用进程,从而可以安全地更新 UI。这就是您将发布和处理结合使用的原因。无法保证会调用该进程,因此您可能需要在每次调用发布后包含一个 Thread.yield

标签: java multithreading


【解决方案1】:

您应该查看Worker Threads and SwingWorkerJavaDocs for SwingWorker,这将为您提供更多信息。

基本上,publish 将值放入队列中,该队列将在 EDT 上下文中传递给 publish 方法。这用于允许将信息从工作人员的后台线程传递到 EDT,在 EDT 中可以使用这些值安全地更新 UI。

应该使用done 方法来获取后台操作的结果(假设它有一个),可以使用get 方法检索该结果,该方法将返回从doInBackground 方法返回的值或者生成的异常

import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.SwingWorker;

public class Test {

    public static void main(String[] args) throws InterruptedException {
        ExampleWorker worker = new ExampleWorker();
        worker.execute();
        Thread.sleep(100);
        try {
            System.out.println(">> " + worker.get());
        } catch (ExecutionException ex) {
            ex.printStackTrace();
        }
    }

    public static class ExampleWorker extends SwingWorker<String, Integer> {

        private int n = 0;
        private long startTime;

        @Override
        protected String doInBackground() throws Exception {
            System.out.println("doInBackground()");
            startTime = System.currentTimeMillis();
            while (System.currentTimeMillis() - startTime < 3000) {
                publish(n++);
                Thread.yield();
            }
            return "What is this used for?";
        }

        @Override
        protected void process(List<Integer> chunks) {
            for (int value : chunks) {
                System.out.println(value);
            }
        }

        @Override
        protected void done() {
            System.out.println("Done: " + n);
        }

    }

}

更新了一个更有针对性的例子...

我没有处理方法,因为我不想在后台操作完成之前将信息传递给 EDT。

那么不要使用publish/process,使用done并返回你想要从doInBackground检索的值

我仍然在我的代码中发布的唯一原因是,如果我不取消该过程,没有它就无法工作。

对我来说似乎工作正常,SwingWorker 已经拥有取消状态

get() 不会阻止 EDT 上的所有内容吗?我不希望这样,因为它会阻止我的 GUI 工作

是的,当 worker 运行时,如果 worker 完成,它将立即返回。在您的示例中,由于没有任何 UI 来阻止 JVM 退出,这是一种阻止 JVM 退出的方法

这意味着您可以使用通常在doInBackground 返回后和/或在PropertyChangeListener 内调用的done,具体取决于您的需要

import java.awt.EventQueue;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

    public static void main(String[] args) throws InterruptedException {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new JLabel("Waiting"));
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);

                ExampleWorker worker = new ExampleWorker();
                worker.addPropertyChangeListener(new PropertyChangeListener() {
                    @Override
                    public void propertyChange(PropertyChangeEvent evt) {
                        if ("state".equals(evt.getPropertyName())) {
                            ExampleWorker worker = (ExampleWorker) evt.getSource();
                            switch (worker.getState()) {
                                case DONE: {
                                    try {
                                        System.out.println("PropertyChange: " + worker.get());
                                    } catch (InterruptedException | ExecutionException ex) {
                                        ex.printStackTrace();
                                    }
                                    frame.dispose();
                                }
                                break;
                            }
                        }
                    }
                });
                worker.execute();
            }
        });
    }

    public static class ExampleWorker extends SwingWorker<Integer, Integer> {

        private int n = 0;
        private long startTime;

        @Override
        protected Integer doInBackground() throws Exception {
            System.out.println("doInBackground()");
            startTime = System.currentTimeMillis();
            while (System.currentTimeMillis() - startTime < 3000) {
                n++;
                Thread.yield();
            }
            return n;
        }

        @Override
        protected void process(List<Integer> chunks) {
            for (int value : chunks) {
                System.out.println(value);
            }
        }

        @Override
        protected void done() {
            try {
                System.out.println("Done: " + get());
            } catch (InterruptedException | ExecutionException ex) {
                ex.printStackTrace();
            }
        }

    }

}

【讨论】:

  • 我没有处理方法,因为我不想在后台操作完成之前将信息传递给 EDT。我仍然在我的代码中发布的唯一原因是因为如果我不取消该过程,它就无法工作。并且 get() 不会阻止 EDT 上的所有内容吗?我不希望这样,因为它会阻止我的 GUI 工作。
  • 如果您不想在 Worker 完成之前更新 UI,则在调用 done 时使用 get 方法,这将获得 doInBackground 返回的值。 get 只会在 worker 运行时阻塞,这就是为什么在 done 方法中使用它是安全的,我只是为了演示目的而将它包含在内,因为我没有你试图解决的整体问题的上下文跨度>
  • 好的,我想我明白了。我按照你的建议在done 中使用了get,这让我取出了publish。然后为了处理取消,我在done 中捕获了CancellationException,并能够从那里打印出n。谢谢!
  • 其实我之前的评论是错误的。我认为这一定是我的计算机有问题或某些溢出错误无声地失败了?如果我在三秒后停止进程,它不起作用,但如果我在 1 秒后停止,它确实有效。
  • 我仍然不知道你最终想要达到什么目标,所以很难提出建议
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-06
  • 2019-06-09
相关资源
最近更新 更多