1

我在一个匿名SwingWorker线程下运行一个非常繁重的进程。同时,我正在使用进度条向 GUI 报告进度。然而,Swing 线程让我陷入了困境。它根本没有及时更新任何东西。我不知道该怎么做,因为我尝试从SwingWorker线程和外部更新 GUI,但都拒绝工作。

如何在繁重的工作线程运行时可靠地更新 Swing UI?

我尝试过的事情

这不起作用(有或没有在invokeLater命令中换行)。

new LocalCompressor(compressor).execute();

while (!compressionDone) {
    SwingUtilities.invokeLater(new Runnable() {
    
        @Override
        public void run() {
            int percent = compressor.getPercentDone();
            progressBar.setValue(percent);
            statusLabel.setText(percent);
        }
    });
}

此外,尝试从并发测量线程更新 UI 不起作用:

class LocalCompressor extends SwingWorker<Void, Void> {

    // [...]
    
    public LocalCompressor(Compressor compressor) {
        this.compressor = compressor;
        
        // [...]
    }
    
    @Override
        protected Void doInBackground() {
        
            final Thread t1 = new Thread(new Runnable() {
            
                @Override 
                public void run(){
                    compressor.compress();
                }
            });
            
            final Thread t2 = new Thread(new Runnable() {
            
                @Override
                public void run() {
                
                    t1.start();
                    
                    while (t1.isAlive()) {
                        updateUI(compressor.getPercentDone());
                    }
                }
            
            });
            
            t2.start();
            
            return null;
        }
        
        // [...]
}
4

5 回答 5

2

你并没有真正使用你的 SwingWorker。工人本身已经是一个线程。如果您有可能将长时间运行的代码放入 中doInBackground(),请将其放在那里。然后只需调用publish(Integer)您的实际进度并处理您在process(List<Integer>)-method 中获得的块。在 process() 中,您可以更新 gui,它位于 EDT 上。

编辑:实际上,你现在正在做的是在几个while循环中轮询,这有点耗电。这就是为什么我认为它对你的算法中的事件更好,每次你得到一个百分比或每次循环开始新一轮或类似的事情。

于 2012-10-03T19:39:11.713 回答
1

扩展此处提供的答案和建议,这是一种编码方式。我假设压缩器本身没有能力做回调,但你可以问它完成的百分比。

在 swingworker 线程 (doInBackground) 中,我们启动真正的压缩线程。然后在后台线程中启动轮询循环,每秒更新几次 UI。要通知 UI 线程,请调用 publish。这将导致在事件线程中定期调用被覆盖的方法进程。从这里我们可以安全地更新进度条和状态标签。

public class LocalCompressor extends SwingWorker<Void, Integer>
{
   private Compressor compressor;

   public LocalCompressor(Compressor compressor)
   {
      this.compressor = compressor;

      // [...]
   }

   @Override
   protected void done()
   {
      System.out.println("Compression is done.  Going to do something with it...");
   }

   @Override
   protected void process(List<Integer> chunks)
   {
      for (Integer percent : chunks)
      {
         progressBar.setValue(percent);
         statusLabel.setText(percent);      
      }
   }

   @Override
   protected Void doInBackground() throws Exception
   {
      final Thread t1 = new Thread(new Runnable()
      {
         @Override
         public void run()
         {
            compressor.compress();
         }
      });

      t1.start();

      while (t1.isAlive())
      {
         int percentDone = compressor.getPercentDone();
         publish(percentDone);
         Thread.sleep(200);
      }
      return null;
   }
}
于 2012-10-03T20:05:17.260 回答
1

您是否尝试过使用 a 的非常简单和基本的方法SwingWorker?就像@Zhedar 之前所说的那样,SwingWorker 本身已经是一个线程。所以删除你的内螺纹(t1t2),然后使用你的耗时compress()方法doInBackground()

一些非常基本的东西,如下所示:

class LocalCompressor extends SwingWorker<Void, Integer> {

    // .....
    // Your constructor here
    // .....

    @Override
    protected Void doInBackground() throws Exception {
        compress();
        return null;
    }

    @Override
    protected void process(List<Integer> chunks) {
        for (Integer chunk : chunks) {
            progressBar.setValue(chunk);
            statusLabel.setText(chunk);
        }
    }
}

现在这个compress()方法应该被移动到里面SwingWorker并且它必须有某个地方 a publish(),在你的情况下它可能是publish(getPercentDone())或者其他什么。

private void compress() {

    // .....

    publish(getPercentDone());

    // .....

}

这就是通常使用SwingWorker.

于 2012-10-03T21:21:52.283 回答
0

我假设(你知道这是怎么回事)调用LocalCompressor.execute()被阻塞。如果是这种情况,您的 while 循环将在全部完成之前不会运行,然后您就违背了在 UI 上获得稳定更新流的目的。

试试这个或类似的东西:

    LocalCompressor comp = new LocalCompressor(compressor);

    SwingUtilities.invokeLater(new Runnable() {

        @Override
        public void run() {
            while (!compressionDone) {
                int percent = compressor.getPercentDone();
                progressBar.setValue(percent);
                statusLabel.setText(percent);
            }
        }
    });
    comp.execute();
}
于 2012-10-03T19:39:43.650 回答
0

您可以使用生产者/消费者模式...

这是一个非常基本的概念......

public class ProducerComsumer {

    public static void main(String[] args) {
        new ProducerComsumer();
    }

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

                JPanel panel = new JPanel(new GridBagLayout());
                panel.setBorder(new EmptyBorder(12, 12, 12, 12));

                JProgressBar progressBar = new JProgressBar();
                panel.add(progressBar);

                JFrame frame = new JFrame();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(panel);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);

                Producer producer = new Producer();
                producer.start();

                Consumer consumer = new Consumer(producer, progressBar);
                consumer.start();
            }
        });
    }

    public class Producer extends Thread {

        private volatile float progress;
        private volatile boolean done;

        public Producer() {
            setPriority(NORM_PRIORITY - 1);
            setDaemon(true);
        }

        public float getProgress() {
            return progress;
        }

        public boolean isDone() {
            return done;
        }

        @Override
        public void run() {
            done = false;
            for (int index = 0; index < Integer.MAX_VALUE; index++) {
                progress = (float) index / (float) Integer.MAX_VALUE;
            }
            done = true;
            System.out.println("All done...");
        }
    }

    public class Consumer extends Thread {

        private Producer producer;
        private JProgressBar progressBar;

        public Consumer(Producer producer, JProgressBar progressBar) {
            setDaemon(true);
            setPriority(NORM_PRIORITY - 1);
            this.producer = producer;
            this.progressBar = progressBar;
        }

        public JProgressBar getProgressBar() {
            return progressBar;
        }

        public Producer getProducer() {
            return producer;
        }

        @Override
        public void run() {
            while (!producer.isDone()) {
                updateProgress();
                try {
                    sleep(1000);
                } catch (InterruptedException ex) {
                    Logger.getLogger(ProducerComsumer.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
            updateProgress();
        }

        protected void updateProgress() {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    int progress = Math.round(getProducer().getProgress() * 100f);
                    System.out.println("Update progress to " + progress);
                    getProgressBar().setValue(progress);
                }
            });
        }
    }
}

玩转这些Thread.setPriority价值观,看看它是否有任何不同

于 2012-10-03T20:45:25.733 回答