如何正确使用ExecutorService来管理同时运行的SwingWorkers数量?

3
我正在根据需要建立多个连接来生成 SwingWorkers。我尝试设置一个固定数量的最大并行 SwingWorkers,当其中一个完成时,另一个将被启动(或许多个已经完成的任务将被启动)。根据http://java.dzone.com/articles/multi-threading-java-swing,我像这样设置基本的SwingWorker:
SwingWorker<Boolean, Void> worker = new SwingWorker<Boolean, Void>() {
        @Override
        protected Boolean doInBackground() throws Exception {

                        System.out.println("One SwingWorker just ran! ");
                }

                return true;
        }

        // Can safely update the GUI from this method.
        protected void done() {

                boolean status;
                try {
                        // Retrieve the return value of doInBackground.
                        status = get();
                        statusLabel.setText("Completed with status: " + status);
                } catch (InterruptedException e) {
                // This is thrown if the thread's interrupted.
                } catch (ExecutionException e) {
                // This is thrown if we throw an exception
                // from doInBackground.
                }
        }


};

worker.execute();

现在我不确定如何实现我上面描述的机制。
https://dev59.com/Q2sy5IYBdhLWcg3wvgdk#8356896我看到我可以使用ExecutorService执行SwingWorker的实例,而这个接口也允许设置线程数:
int n = 20; // Maximum number of threads
ExecutorService threadPool = Executors.newFixedThreadPool(n);
SwingWorker w; //don*t forget to initialize
threadPool.submit(w);

我认为这就是我需要的东西,但我不知道如何将所有内容组合在一起(..我对Java也很陌生..)。有人可以在实现过程中给我一些指导吗?比如说,在顶部我有int totalTask = 100;。也许只是一些循环的问题,但我似乎找不到任何真正易于理解的示例,而且我还不能完全理解它,所以.. 我会感激一些帮助!谢谢。
更新: 我已经按照以下方式设置了ExecutorService:
ExecutorService executorService = Executors.newFixedThreadPool(500);

for (int i = 0; i < 20 ; i++) {

    executorService.submit(worker); 
    //I tried both...
    //executorService.execute(worker);
}


我已经移除了上述SwingWorker后面的worker.execute()调用,但是从控制台输出仅有一行"One SwingWorker just ran!",这是为什么?我做错了什么吗?


1
正如我在你之前的问题中注意到的那样,请小心处理同时运行的SwingWorkers实例的数量,SwingWorker是从Future初始化的,并没有保证每个实例都在相同的时间结束或在另一个实例启动之前完成(对于使用Thread的逻辑也是有效的,但没有bug),使用Runnable#Thread的逻辑更好、更容易、更舒适,在done()中使用get()捕获可能出现的异常,而不是用于返回。 - mKorbel
在这种情况下,我对它们完成的顺序不感兴趣。不确定您所说的“但没有错误”是什么意思。在我阅读的所有SwingWorker文档中,done()和get()分别用于发出回调方法,该方法1)可以访问SwingWorker doInBackground()的返回值,2)可以安全地更新GUI(因为它会自动在EDT中启动)。我不确定您是否指的是其他get()和done()方法,也不知道它们是否也用于Runnable#Thread。 - Redoman
  1. 你可以通过get()方法捕获异常或返回值。(来源:https://dev59.com/COo6XIcBkEYKwwoYSClJ)
  2. publish()/setProcess方法
- mKorbel
嘿...我在这里也遇到了同样的问题。但是,我的所有SwingWorker都很好,它们能很好地完成工作。唯一奇怪的部分是done()方法只被调用一次,就像你所经历的那样。 - gumuruh
4个回答

1

你可以这样做:

  • 使用固定的线程池来初始化executorService,就像你展示的那样。
  • 在循环中创建你的runnable,需要多少个runnable就创建多少个。
  • 你可以有50个线程和5000个runnable。在完成前50个任务后,任何空闲的线程都将处理第51个任务,以此类推。
  • 使用你的Runnable调用executorservice的execute方法。
  • 一旦所有任务都完成,就关闭executor service。

就像这样:

ExecutorService executorService = Executors.newFixedThreadPool(500);

for (long i = 0; i < 1000000; i++) {
    Runnable populator = new YourRunnable();
    executorService.execute(populator);
}
executorService.shutdown();
while(!executorService.isTerminated()){
}

isTerminated可以用来检查executorServices是否已经关闭。由于在调用shutdown()方法后仍可能有多个执行线程正在运行(因为它们尚未完成任务),所以while循环的作用类似于等待调用。

另外一个关键点是:无论你想要传递什么给ExecutorService,它必须是Runnable实现。在你的情况下,你的SwingWorker必须是一个Runnable。


那么..嗯..如何将SwingWorker转换为Runnable? - Redoman
我不应该这样做,但是通过一些谷歌搜索,我找到了这个链接:http://greatratrace.blogspot.in/2010/01/throttling-swingworker-using.html。你可以根据自己的需要进行调整。 - Praba
我知道,但是加上对Java的6天经验,你就能明白了。 - Redoman
抱歉,可能是因为我的经验不足,我没有看到你写的示例代码(在那里我只需要将我的SwingWorker转换成可运行的)与你链接的页面之间的联系,这似乎采用了不同的方法,所以我不知道如何将其适应我的代码。另外,我想尽可能地保持我的代码简单,第一种方法看起来更容易。你能详细说明一下这个方法吗? - Redoman
我的错,我没有意识到SwingWorker已经实现了Runnable接口。所以这里不需要额外的东西。我提供的代码展示了如何一般地使用执行器服务。链接展示了如何在执行器服务中使用SwingWorker。你所要做的就是创建执行器服务并将你的SwingWorker传递给它。 - Praba
显示剩余2条评论

1

很有意思。使用具有有限池的ExecutorService时,您只需要在需要时提交工作程序,只有那些数量的工作程序将同时执行。

我制作了这个小测试应用程序,您可以按下按钮以任何速度提交一些工作程序,并且您可以看到任何给定时间执行的工作程序数量永远不会高于您初始化ExecutorService的值numberOfThreads

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;

public class SwingWorkerExecutorTest
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                new SwingWorkerExecutorTest();
            }
        });
    }


    public SwingWorkerExecutorTest()
    {
        JFrame frame = new JFrame("Frame");

        int numberOfThreads = 2; //1 so they are executed one after the other.
        final ExecutorService threadPool = Executors.newFixedThreadPool(numberOfThreads);

        JButton button1 = new JButton("Submit SwingWorker 1");
        button1.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                String workerName = "Worker 1";
                appendMessage("Submited " + workerName);
                SwingWorker worker = new TestWorker(workerName);
                threadPool.submit(worker);
            }
        });

        JButton button2 = new JButton("Submit SwingWorker 2");
        button2.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                String workerName = "Worker 2";
                appendMessage("Submited " + workerName);
                SwingWorker worker = new TestWorker(workerName);
                threadPool.submit(worker);
            }
        });

        JButton button3 = new JButton("Submit SwingWorker 3");
        button3.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                String workerName = "Worker 3";
                appendMessage("Submited " + workerName);
                SwingWorker worker = new TestWorker(workerName);
                threadPool.submit(worker);
            }
        });

        JPanel buttonsPanel = new JPanel();
        buttonsPanel.add(button1);
        buttonsPanel.add(button2);
        buttonsPanel.add(button3);
        frame.add(buttonsPanel, BorderLayout.PAGE_END);

        _textArea = new JTextArea("Submit some workers:\n");
        _textArea.setEditable(false);
        frame.add(new JScrollPane(_textArea));

        frame.setSize(600, 400);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }


    private class TestWorker extends SwingWorker
    {
        public TestWorker(String name)
        {
            _name = name;
        }

        @Override
        protected Object doInBackground() throws Exception
        {
            String message = "A " + _name + " has started!";
            appendMessage(message);
            doHardWork();
            return null;
        }

        @Override
        protected void done()
        {
            String message = "A " + _name + " has finished!";
            appendMessage(message);
        }

        private void doHardWork()
        {
            try
            {
                Thread.sleep(2000);
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }

        private String  _name;
    }

    private static void appendMessage(String message)
    {
        _textArea.append(message + "\n");
        System.out.println(message);
    }

    private static JTextArea    _textArea;
}

它看起来像这样:

example

例如,如果您设置了2个线程,当您提交大量的工作时,它会每次执行2个工作。

0
请查看SwingWorker的源代码。 您可以找到类似于execute()方法的内容。
 public final void execute() {
    getWorkersExecutorService().execute(this);
}

此时,您可以创建自己的ExecutorService并管理池

 SwingWorker<Boolean, Void> test = new SwingWorker<Boolean, Void>() {
        private ExecutorService service =  new ThreadPoolExecutor(5, 10,
                10L, TimeUnit.MINUTES,
                new LinkedBlockingQueue<Runnable>(),
                new ThreadFactory() {
                    AtomicInteger count= new AtomicInteger();
                    @Override
                    public Thread newThread(Runnable r) {
                        return new Thread("Pooled SwingWorker " + count.getAndAdd(1));
                    }
                });
        @Override
        protected Boolean doInBackground() throws Exception {
            return true;
        }

        public void doIt() {
            service.execute(this);
        }

    };

0

当你想到那一刻:原来如此明显!

ExecutorService executorService = Executors.newFixedThreadPool(20);

for (int i = 0; i < 500; i++) {

        SwingWorker<Boolean, Void> worker = new SwingWorker<Boolean, Void>() {
                @Override
                protected Boolean doInBackground() throws Exception {

                        System.out.println("One SwingWorker just ran!");
                        return true;
                }


                protected void done() {

                        boolean status;
                        try {

                                status = get();

                        } catch (InterruptedException e) {
                        // This is thrown if the thread's interrupted.
                        } catch (ExecutionException e) {
                        // This is thrown if we throw an exception
                        // from doInBackground.
                        }
                }

        };


        executorService.submit(worker);
}


它运行得非常好!


一切都正常,但是... done() 方法只被一个提交的进程调用了一次? - gumuruh

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