【问题标题】:Multi-threading with GUI in JavaJava中带有GUI的多线程
【发布时间】:2018-02-12 09:35:15
【问题描述】:

我正在为一个计算密集型程序的 GUI 工作,并且需要一些时间才能完成计算。我想在 GUI 上显示和更新处理时间,以供参考和向用户指示程序正在运行。我创建了一个工作人员来处理单独线程上的处理时间,如下所示:

public class Worker extends SwingWorker<String, String>{
    
    JLabel label;
    boolean run;
    
    public Worker(JLabel label)
    {
        this.label = label;
        this.run = true;
    }

    @Override
    protected String doInBackground() throws Exception {
        //This is what's called in the .execute method
        long startTime = System.nanoTime();
        while(run)
        {
            //This sends the results to the .process method
            publish(String.valueOf(System.nanoTime() - startTime));
            Thread.sleep(100);
        }
        return null;
    }
    
    public void stop()
    {
        run = false;
    }

    @Override
    protected void process(List<String> item) {
        double seconds = Long.parseLong(item.get(item.size()-1))/1000000000.0;
        String secs = String.format("%.2f", seconds);
        //This updates the UI
        label.setText("Processing Time: " + secs + " secs");
        label.repaint();
    }
}

我将 JLabel 传递给显示处理时间的 Worker。以下代码创建了 Worker 并执行了一个执行主要计算的 runnable。

Worker worker = new Worker(jLabelProcessTime);
worker.execute();
//Check for results truncation
boolean truncate = !jCheckBoxTruncate.isSelected();
long startTime = System.nanoTime();
String[] args = {fileName};
//run solution and draw graph
SpeciesSelection specSel = new SpeciesSelection(args, truncate);
Thread t = new Thread(specSel);
t.start();
t.join();
ArrayList<Double> result = specSel.getResult();
drawGraph(result);
worker.stop();

我的问题是,直到计算完成后,GUI 上的处理时间才会更新。我想我已经很接近了,因为没有 't.join();'计时器更新正常,但处理永远不会完成。我真的很感激能帮助您找出问题所在。

【问题讨论】:

  • 在 Swing 中的一个基本规则是使用SwingUtilities.invokeLater(Runnable) 以便在您在 不同 线程中进行计算之后将 GUI 更新任务添加到 Swing 的队列中而不是图形用户界面线程。 Swing 的引擎将负责线程安全地应用您的更新
  • 你在哪里t.join();?看起来您是在阻止它的 UI 线程上执行此操作...
  • 代码的下半部分在一个action执行的事件代码块中。

标签: java multithreading swing user-interface


【解决方案1】:

你的代码没有像你想象的那样工作......

我为你创建了MVCE...

import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingWorker;

public class SwingWorkerTest extends JFrame {

    public SwingWorkerTest() {
        this.setLayout(new FlowLayout());
        JButton button = new JButton("run");
        JLabel label = new JLabel("time: -");
        button.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                Worker worker = new Worker(label);
                worker.execute();
                //Check for results truncation
//              boolean truncate = !jCheckBoxTruncate.isSelected();
//              long startTime = System.nanoTime();
//              String[] args = {fileName};
                //run solution and draw graph
//              SpeciesSelection specSel = new SpeciesSelection(args, truncate);
//              Thread t = new Thread(specSel);
//              t.start();
//              t.join();
//              ArrayList<Double> result = specSel.getResult();
//              drawGraph(result);
                worker.stop();

                System.out.println("button's actionPerformed finished");
            }
        });

        this.getContentPane().add(button);
        this.getContentPane().add(label);
        this.setDefaultCloseOperation(EXIT_ON_CLOSE);
        this.setVisible(true);
    }

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

class Worker extends SwingWorker<String, String>{

    JLabel label;
    boolean run;

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

    @Override
    protected String doInBackground() throws Exception {
        System.out.println("doInBackground..., run=" + run);
        //This is what's called in the .execute method
        long startTime = System.nanoTime();
//        while(run)
//        {
            System.out.println("running...");
            //This sends the results to the .process method
            publish(String.valueOf(System.nanoTime() - startTime));
            Thread.sleep(100);
//        }
        System.out.println("worker finished...");
        return null;
    }

    public void stop()
    {
//      System.out.println("stop");
//        run = false;
    }

    @Override
    protected void process(List<String> item) {
        System.out.println("processed");
        double seconds = Long.parseLong(item.get(item.size()-1))/1000000000.0;
        String secs = String.format("%.2f", seconds);
        //This updates the UI
        System.out.println("updating");
        label.setText("Processing Time: " + secs + " secs");
//        label.repaint();
    }
}

简而言之,我发现 Worker.stop()doInBackground 之前被调用,因此你的运行是错误的,因此永远不会调用 publish

上面打印的“固定”代码(开始后我调整了大小并点击了运行按钮):

button's actionPerformed finished
doInBackground..., run=true
running...
processed
updating
worker finished...

它显示:


使用计时器的新方法

import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingWorker;
import javax.swing.SwingWorker.StateValue;
import javax.swing.Timer;

public class SwingWorkerTestNew extends JFrame {

    int progress = 0;

    public SwingWorkerTestNew() {
        GridLayout layout = new GridLayout(2, 1);
        JButton button = new JButton("run");
        JLabel label = new JLabel("progress: -");
        WorkerNew worker = new WorkerNew(label);
        button.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                worker.execute();
                System.out.println("button's actionPerformed finished");
            }
        });

        this.getContentPane().setLayout(layout);
        this.getContentPane().add(button);
        this.getContentPane().add(label);

        this.setDefaultCloseOperation(EXIT_ON_CLOSE);
        this.pack();
        this.setVisible(true);

        Timer timer = new Timer(100, new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                if (worker.getState() == StateValue.STARTED) {
                    ++progress;
                    label.setText(Integer.toString(progress));
                }
                if (worker.getState() == StateValue.DONE) {
                    label.setText("done");
                }
            }
        });
        timer.start();
    }

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

class WorkerNew extends SwingWorker<String, String> {

    JLabel label;

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

    @Override
    protected String doInBackground() throws Exception {
        System.out.println("background");
        Thread.sleep(2000);
        System.out.println("done");
        return null;
    }

}

【讨论】:

  • 谢谢。我仍然不知道如何解决我的问题。我是否正确地说您的意图是展示问题而不是提供解决方案?还是我错过了什么?
  • 您将问题描述为“处理时间不会在 GUI 上更新”。如您所见,“按钮的操作已完成”是控制台中的第一条消息,因此它实际上是在后台运行的。如果您将 Thred.sleep(100) 移动到 publish 之前,它将显示 0.10 秒。你想解决什么问题?
  • 我希望 GUI 上显示的时间每 100 毫秒更新一次,直到线程 t 的工作完成。
  • 在这种情况下,我会使用一些守护线程,它会询问您的任务是否完成,请参阅getState
【解决方案2】:

我以一种过于复杂的方式来解决这个问题。不需要 SwingWorker。我解决了如下:

//Check for results truncation
boolean truncate = !jCheckBoxTruncate.isSelected();
String[] args = {fileName};
//run solution and draw graph
SpeciesSelection specSel = new SpeciesSelection(args, truncate);
Thread t = new Thread(specSel);
t.start();
long startTime = System.nanoTime();

new Thread()
{
   public void run() {
   while(!specSel.isFinished())
   {
    double seconds = (System.nanoTime() - startTime)/1000000000.0;
    String secs = String.format("%.2f", seconds);
    jLabelProcessTime.setText("Processing Time: " + secs + " secs");
    try {
        Thread.sleep(100);
      } catch (InterruptedException ex) {
        Logger.getLogger(SpecSelGUI.class.getName()).log(Level.SEVERE, null, ex);
      }
   }
ArrayList<Double> result = specSel.getResult();
    drawGraph(result);
   }
}.start();

【讨论】:

  • jLabelProcessTime.setText("Processing Time: " + secs + " secs");换成SwingUtilities.invokeLater(new Runnable { public void run() { jLabelProcessTime.setText("Processing Time: " + secs + " secs"); } } );会更正确
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-10-30
  • 1970-01-01
  • 2012-03-18
  • 1970-01-01
  • 1970-01-01
  • 2012-03-19
  • 1970-01-01
相关资源
最近更新 更多