如何从 EDT 向长时间运行的 SwingWorker 交付对象?

问题描述

EDT 如何与正在执行的 SwingWorker 通信? SwingWorker 有很多方法可以将信息传送回 EDT - 例如发布/处理和属性更改,但没有定义的方式(我见过)在另一个方向进行通信。似乎很好的旧 Java 并发线程间通信将是通过 wait()notify() 的方式。这不起作用。我稍后会解释。我实际上让它工作了,但它使用了一个丑陋的轮询机制。我觉得应该有更好的方法。这是我试图完成的过程:

  1. 用户从主 Swing UI (EDT) 启动 SwingWorker 长时间运行的任务(引擎)。
  2. 在某些时候,引擎需要来自 EDT 的信息,以便将这些信息反馈给 EDT。这可以通过可见 UI 组件的发布/处理更新来完成。重要的是,这一步不会阻止 EDT,因为其他事情也在发生。
  3. 引擎阻塞等待答案。
  4. 在某些时候,用户会注意到视觉指示,并通过某些 UI (EDT) 功能(例如按下 Swing 按钮)提供所需信息。
  5. EDT 更新引擎上的对象。然后“唤醒”引擎。
  6. 引擎引用更新后的对象并继续处理。

我对 wait()/notify() 的问题是,在第 3 步中,在 wait() 中对 doInBackground() 的任何调用都会导致 done() 方法立即被触发和要终止的 SwingWorker。 通过在 sleep() 中使用丑陋的 doInBackground() 循环,我能够使上述过程起作用:

for (;;)
{
    Thread.sleep(10);

    if (fromEDT != null)
    {
        // Process the update from the EDT
        System.out.println("From EDT: " + fromEDT);
        fromEDT = null;
        break;
    }
}

在第 5 步中,引擎会自行唤醒并检查来自 EDT 的更新。

这是最好的方法吗?我有点怀疑。

解决方法

以下是一个演示 SwingWorker 暂停并等待用户输入的代码:

import java.awt.*;
import java.util.List;
import javax.swing.*;

public class SwingWorkerWaitDemo {

    public static void creategui(){

        JFrame f = new JFrame("SwingWorker wait Demo");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setLocationRelativeTo(null);
        f.add(new MainPanel());
        f.pack();
        f.setVisible(true);
    }

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

class MainPanel extends JPanel {

    private static final String BLANK = "               ";

    private MyWorker swingWorker;
    private final JLabel output,msg;
    private final JButton start,stop,respond;

    MainPanel() {
        setLayout(new BorderLayout(2,2));
        start = new JButton("Start");
        start.addActionListener(e->start());
        stop = new JButton("Stop");
        stop.setEnabled(false);
        stop.addActionListener(e->stop());
        JPanel ssPane = new JPanel(new FlowLayout(FlowLayout.CENTER));
        ssPane.add(start); ssPane.add(stop);
        add(ssPane,BorderLayout.PAGE_START);

        output = new JLabel(BLANK);
        JPanel outputPane = new JPanel(new FlowLayout(FlowLayout.CENTER));
        outputPane.add(output);
        add(outputPane,BorderLayout.CENTER);

        msg = new JLabel(BLANK);
        respond = new JButton("Respond");
        respond.addActionListener(e->respond());
        respond.setEnabled(false);
        JPanel responsePane = new JPanel();
        responsePane.add(msg); responsePane.add(respond);
        add(responsePane,BorderLayout.PAGE_END);
    }

    @Override
    public Dimension getPreferredSize(){
        return new Dimension(400,200);
    }

    private void start() {
        start.setEnabled(false);
        stop.setEnabled(true);
        swingWorker = new MyWorker();
        swingWorker.execute();
    }

    private void stop() {
        stop.setEnabled(false);
        swingWorker.setStop(true);
    }

    private void message(String s){
        msg.setText(s);
    }

    private void clearMessage(){
        msg.setText(BLANK);
    }

    private void askForUserResponse(){
        respond.setEnabled(true);
        message("Please respond " );
    }

    private void respond(){
        clearMessage();
        respond.setEnabled(false);
        swingWorker.setPause(false);
    }

    class MyWorker extends SwingWorker<Integer,Integer> {

        private boolean stop = false;
        private volatile boolean pause = false;

        @Override
        protected Integer doInBackground() throws Exception {

            int counter = 0;

            while(! stop){
                publish(counter++);
                if(counter%10 == 0) {
                    pause = true;
                    askForUserResponse();
                    while(pause){   /*wait*/ }
                }
                Thread.sleep(500);
            }
            return counter;
        }

        @Override
        protected void process(List<Integer> chunks) {
            for (int i : chunks) {
                output.setText(String.valueOf(i));
            }
        }

        @Override
        protected void done() {
            message("All done");
        }

        void setStop(boolean stop) {
            this.stop = stop;
        }

        void setPause(boolean pause) {
            this.pause = pause;
        }
    }
}