等待多个SwingWorkers

12
请考虑以下代码片段:
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.InvocationTargetException;
import javax.swing.*;

public class TestApplet extends JApplet
{
    @Override
    public void init()
    {
        try
        {
            SwingUtilities.invokeAndWait(new Runnable()
            {
                @Override
                public void run()
                {
                    createGUI();
                }
            });
        }
        catch(InterruptedException | InvocationTargetException ex)
        {
        }
    }

    private void createGUI()
    {
        getContentPane().setLayout(new FlowLayout());
        JButton startButton = new JButton("Do work");
        startButton.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent ae)
            {
                JLabel label = new JLabel();
                new Worker(label).execute();
            }
        });
        getContentPane().add(startButton);
    }

    private class Worker extends SwingWorker<Void, Void>
    {
        JLabel label;

        public Worker(JLabel label)
        {
            this.label = label;
        }

        @Override
        protected Void doInBackground() throws Exception
        {
            // do work
            return null;
        }

        @Override
        protected void done()
        {
            getContentPane().remove(label);
            getContentPane().revalidate();
        }
    }
}

在应用程序中添加一个标签,用于显示工作线程(使用publish/process方法)的一些中间结果。最后,从applet的面板中删除该标签。我的问题是,如何创建多个带有其自己的工作线程的标签,并在它们完成任务后将它们全部删除?

提前感谢。

更新:

我希望这能澄清我的问题。我希望所有的标签在所有的工作线程完成任务后一次性删除,而不是每个工作线程完成任务后立即删除。

更新2:

以下代码似乎可以实现我所需的功能。请评论我是否做对了。我感觉有些问题。一个问题是按钮右侧的标签仍然可见,尽管它们已被删除。setVisible(false)似乎解决了这个问题。这样做的方式正确吗?

import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.InvocationTargetException;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.swing.*;

public class TestApplet extends JApplet
{
    private Queue<JLabel> labels = new LinkedList<>();
    private static final Random rand = new Random();

    @Override
    public void init()
    {
        try
        {
            SwingUtilities.invokeAndWait(new Runnable()
            {
                @Override
                public void run()
                {
                    createGUI();
                }
            });
        }
        catch(InterruptedException | InvocationTargetException ex){}
    }

    private void createGUI()
    {
        getContentPane().setLayout(new FlowLayout());
        JButton startButton = new JButton("Do work");
        startButton.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent ae)
            {
                ExecutorService executor = Executors.newFixedThreadPool(10);
                for(int i = 0; i < 10; i++)
                {
                    JLabel label = new JLabel();
                    getContentPane().add(label);
                    executor.execute(new Counter(label));
                }
            }
        });
        getContentPane().add(startButton);
    }

    private class Counter extends SwingWorker<Void, Integer>
    {
        private JLabel label;

        public Counter(JLabel label)
        {
            this.label = label;
        }

        @Override
        protected Void doInBackground() throws Exception
        {
            for(int i = 1; i <= 100; i++)
            {
                publish(i);
                Thread.sleep(rand.nextInt(80));
            }

            return null;
        }

        @Override
        protected void process(List<Integer> values)
        {
            label.setText(values.get(values.size() - 1).toString());
        }

        @Override
        protected void done()
        {
            labels.add(label);

            if(labels.size() == 10)
            {
                while(!labels.isEmpty())
                    getContentPane().remove(labels.poll());

                getContentPane().revalidate();
            }
        }
    }
}

您也可以考虑将它们放入列表 (JList) 中。 - Andrew Thompson
@AndrewThompson,是的,但我应该把等待它们的代码放在哪里呢?它应该是另一个SwingWorker生成其他几个吗? - Vlad
请不要吞噬异常。 - trashgod
@trashgod 谢谢。绝对没错。这只是一个例子。 - Vlad
2个回答

16

当所有工人完成任务时,我打算一起删除所有的标签。

此处所述,在这种情况下,可以使用CountDownLatch。在下面的示例中,每个工人在完成任务时调用latch.countDown(),而Supervisor工作线程会在latch.await()上阻塞,直到所有任务完成。为了演示目的,Supervisor更新标签。虽然在注释中展示了批量删除的方法,但在技术上是可行的,但通常不建议这样做。相反,请考虑使用JListJTable

Worker Latch Test

import java.awt.Color;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.swing.*;

/**
* @see https://dev59.com/d2gu5IYBdhLWcg3wYWFX#11372932
* @see https://dev59.com/3VDTa4cB1Zd3GeqPGRYV#3588523
*/
public class WorkerLatchTest extends JApplet {

    private static final int N = 8;
    private static final Random rand = new Random();
    private Queue<JLabel> labels = new LinkedList<JLabel>();
    private JPanel panel = new JPanel(new GridLayout(0, 1));
    private JButton startButton = new JButton(new StartAction("Do work"));

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.setTitle("Test");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new WorkerLatchTest().createGUI());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    @Override
    public void init() {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                add(new WorkerLatchTest().createGUI());
            }
        });
    }

    private JPanel createGUI() {
        for (int i = 0; i < N; i++) {
            JLabel label = new JLabel("0", JLabel.CENTER);
            label.setOpaque(true);
            panel.add(label);
            labels.add(label);
        }
        panel.add(startButton);
        return panel;
    }

    private class StartAction extends AbstractAction {

        private StartAction(String name) {
            super(name);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
                startButton.setEnabled(false);
                CountDownLatch latch = new CountDownLatch(N);
                ExecutorService executor = Executors.newFixedThreadPool(N);
                for (JLabel label : labels) {
                    label.setBackground(Color.white);
                    executor.execute(new Counter(label, latch));
                }
                new Supervisor(latch).execute();
        }
    }

    private class Supervisor extends SwingWorker<Void, Void> {

        CountDownLatch latch;

        public Supervisor(CountDownLatch latch) {
            this.latch = latch;
        }

        @Override
        protected Void doInBackground() throws Exception {
            latch.await();
            return null;
        }

        @Override
        protected void done() {
            for (JLabel label : labels) {
                label.setText("Fin!");
                label.setBackground(Color.lightGray);
            }
            startButton.setEnabled(true);
            //panel.removeAll(); panel.revalidate(); panel.repaint();
        }
    }

    private static class Counter extends SwingWorker<Void, Integer> {

        private JLabel label;
        CountDownLatch latch;

        public Counter(JLabel label, CountDownLatch latch) {
            this.label = label;
            this.latch = latch;
        }

        @Override
        protected Void doInBackground() throws Exception {
            int latency = rand.nextInt(42) + 10;
            for (int i = 1; i <= 100; i++) {
                publish(i);
                Thread.sleep(latency);
            }
            return null;
        }

        @Override
        protected void process(List<Integer> values) {
            label.setText(values.get(values.size() - 1).toString());
        }

        @Override
        protected void done() {
            label.setBackground(Color.green);
            latch.countDown();
        }
    }
}

1
不客气;像这样的混合小程序/应用程序使用 [tag:java-web-start] 提供了多种部署选项,如此所示:https://sites.google.com/site/drjohnbmatthews/subway。 - trashgod
+1 优秀的答案和代码示例!(挑剔与问题核心无关:使用ActionListener,咳咳 :-)) - kleopatra
@kleopatra:谢谢您的提醒;Action更加灵活。 - trashgod
您能否详细阐述在这种情况下使用Action的优点,而不是ActionListener? - Vlad
1
是的,但不如相关的教程(http://docs.oracle.com/javase/tutorial/uiswing/misc/action.html)那样好。 - trashgod
你的代码帮了我很多,谢谢 :)。(PS:+1 是隐含的...) - Radu Murzea

1
您已经有了相应的代码,但需要在按钮被点击时将标签添加到内容面板中。可以参考以下示例代码:
 JLabel label = new JLabel();
 getContentPane().add(label);
 getContentPane().validate();
 new Worker(label).execute();

在标签中添加一些文本,这样当它被添加到屏幕上时,您实际上可以看到它,这可能是一个好主意。

 JLabel label = new JLabel("Hello...I am here");

最后,在doInBackground()方法中,您可以添加一些代码来更新标签,以便在某个任务正在运行时进行显示:
 for(int i = 0;i < 100; i++){
            Thread.sleep(20);
            label.setText("Counting..." + i);
  }

这样你实际上可以看到任务正在运行。如果您多次单击按钮,则会看到多个标签,每个标签在任务完成后都会消失。


感谢您的快速回复。在将代码粘贴到这里之前,我对其进行了简化。标签确实有一些文本,并添加到内容窗格中。:) 虽然感谢您的建议,但我认为我没有清楚地表达我的问题。我的意图是在所有工作人员完成任务时一起删除所有标签。 - Vlad

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接