SwingWorker和命令行处理中断

时间:2016-08-24 19:11:21

标签: java swing command-line concurrency swingworker

我正在尝试构建一个Swing解决方案,用于压缩在rar命令行上中继的文件。由于GUI需要保持响应,我已将用于处理命令行的代码包装到SwingWorker类中。

SwingWorker<Boolean, String> worker = new SwingWorker<Boolean, String>(){
        protected Boolean doInBackground() throws Exception {

            Runtime rt = Runtime.getRuntime();

            try {
                //daj processu da ode u background nekako, da ga ne sjebem sa ctrl + c (winrar umesto rar)
                String command = "my command, this works just fine";

                Process p = rt.exec(command, null, new File("C:\\Program Files\\WinRar"));


                BufferedReader stdInput = new BufferedReader(new 
                         InputStreamReader(p.getInputStream()));
                String s = null;
                System.out.println("<INPUT>");
                while ((s = stdInput.readLine()) != null) {
                    System.out.println(s);
                }
                System.out.println("</INPUT>");
                InputStream stderr = p.getErrorStream();
                InputStreamReader isr = new InputStreamReader(stderr);
                BufferedReader br = new BufferedReader(isr);

                System.out.println("<ERROR>");
                String line = null;
                while ( (line = br.readLine()) != null){
                    System.out.println(line);
                    return false;

                }
                System.out.println("</ERROR>");
                int exitVal = p.waitFor();
                //EXIT VALUE IS ALWAYS 0, EVEN IF I INTERRUPT IT WITH CTRL+C
                System.out.println("Process exitValue: " + exitVal);



            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                return false;
            } catch (InterruptedException e) {
                e.printStackTrace();
                return false;
            } catch (Exception e) {
                return false;
            }

            return true;
        }
        @Override
        protected void process(List<String> chunks) {
            // TODO Auto-generated method stub
            //SOME GUI UPDATES
        }
        @Override
        protected void done() {
            // TODO Auto-generated method stub
            Boolean status = false;
            try {
                status = get();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (ExecutionException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

            //MORE GUI UPDATES
            if(status){
                tableList.setValueAt("Done", row, 3);
            } else{
                tableList.setValueAt("Error", row, 3);
            } 
            super.done();
        }
    };
    worker.execute();

当我删除输入和错误的打印时,一旦rar出现在屏幕上就会打印退出值。所以我的代码中没有“waitFor()”方法的实际意义。我需要的是检查rar是否在没有中断的情况下关闭(如CTRL + C,或在cmd窗口上点击“X”)并获取退出代码。我已经尝试在运行时添加关闭钩子(rt变量),但是当我关闭整个GUI时它会做出反应。

1 个答案:

答案 0 :(得分:3)

您需要获取输入和错误流,并在每个线程中读取它们。现在你的错误流永远不会有机会,因为它前面的阻塞while循环。

我使用了以下代码(虽然已经很久了......):

枚举:GobblerType.java

enum GobblerType {
   ERROR, OUTPUT
}

类StreamGobbler.java

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;

public class StreamGobbler implements Runnable {

   private InputStream is;
   private GobblerType type;
   private OutputStream os;

   public StreamGobbler(InputStream is, GobblerType type) {
      this(is, type, null);
   }

   public StreamGobbler(InputStream is, GobblerType type, OutputStream redirect) {
      this.is = is;
      this.type = type;
      this.os = redirect;
   }

   public void run() {
      try {
         PrintWriter pw = null;
         if (os != null) {
            pw = new PrintWriter(os, true);
         }
         InputStreamReader isr = new InputStreamReader(is);
         BufferedReader br = new BufferedReader(isr);
         String line = null;
         while ((line = br.readLine()) != null) {
            if (pw != null) {
               pw.println(line);
            }
         }
      } catch (IOException ioe) {
         ioe.printStackTrace();
      }
   }
}

然后像这样使用它:

Process proc = Runtime.getRuntime().exec(.....);  // TODO: Fix!

StreamGobbler errorGobbler = new StreamGobbler(proc.getErrorStream(), GobblerType.ERROR);
StreamGobbler outputGobbler = new StreamGobbler(proc.getInputStream(), GobblerType.OUTPUT);

new Thread(errorGobbler).start();
new Thread(outputGobbler).start();

int exitVal = proc.waitFor();
proc.destroy();    

好的,我创建了一些代码作为概念验证程序。我稍微修改了我的Gobbler,因此它不需要OutputStream,而是使用PropertyChangeListener来通知侦听器来自InputStream的任何文本。为了实现这一点,我的所有代码都在同一个包中,并注意包名是关键,你可能需要更改你的。运行此代码确实按预期运行。它有点过于简单,可能应该使用某种类型的阻塞队列来在类之间传递信息。

GobblerType.java

用于区分正在使用的两种流gobblers的枚举

package pkg2;

public enum GobblerType {
    ERROR, OUTPUT
}

StreamGobbler2.java

使用输入流阅读器从输入流中获取文本的流gobbler,将文本放入text字段,并通知侦听器新文本。它使用PropertyChangeListener进行通知。这对生产者 - 消费者来说是一种粗糙的方式,并且有可能无法捕获所有传递的信息更好的方法是使用某种阻塞队列。

package pkg2;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.concurrent.Callable;

public class StreamGobbler2 implements Callable<Void> {
    private PropertyChangeSupport support = new PropertyChangeSupport(this);
    private InputStream is;
    private GobblerType type;
    private String text;

    public StreamGobbler2(InputStream is, GobblerType type) {
        this.is = is;
        this.type = type;
    }

    @Override
    public Void call() throws Exception {
        InputStreamReader isr = new InputStreamReader(is);
        BufferedReader br = new BufferedReader(isr);
        String line = null;
        while ((line = br.readLine()) != null) {
            setText(line);
        }
        return null;
    }

    public GobblerType getType() {
        return type;
    }

    public void addPropertyChangeListener(PropertyChangeListener listener) {
        support.addPropertyChangeListener(listener);
    }

    public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
        support.addPropertyChangeListener(propertyName, listener);
    }

    public void setText(String text) {
        String oldValue = null;
        String newValue = text;
        this.text = text;
        support.firePropertyChange(type.toString(), oldValue, newValue);
    }

    public String getText() {
        return text;
    }

}

ProcessLauncher.java

这是一个非Swing类,可以捕获两个gobblers的信息。同样,更好的方法是使用阻塞队列(下一次迭代)

package pkg2;

import java.beans.PropertyChangeListener;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ProcessLauncher implements Callable<Integer> {
    private ExecutorService execService = Executors.newFixedThreadPool(2);
    private List<String> commands;
    private List<PropertyChangeListener> listeners = new ArrayList<>();

    public ProcessLauncher(List<String> commands) {
        this.commands = commands;
    }

    public void addPropertyChangeListener(PropertyChangeListener listener) {
        listeners.add(listener);
    }

    @Override
    public Integer call() throws Exception {
        ProcessBuilder pb = new ProcessBuilder(commands);
        Process p = pb.start();
        int exitValue = 0;

        try (InputStream inputStream = p.getInputStream();
             InputStream errorStream = p.getErrorStream()) {

            StreamGobbler2 errorGobbler = new StreamGobbler2(inputStream, GobblerType.OUTPUT);
            StreamGobbler2 outputGobbler = new StreamGobbler2(errorStream, GobblerType.ERROR);

            for (PropertyChangeListener listener : listeners) {
                errorGobbler.addPropertyChangeListener(listener);
                outputGobbler.addPropertyChangeListener(listener);                
            }

            List<Future<Void>> futures = new ArrayList<>();
            futures.add(execService.submit(errorGobbler));
            futures.add(execService.submit(outputGobbler));
            execService.shutdown();

            exitValue = p.waitFor();
            for (Future<Void> future : futures) {
                future.get();
            }
        }

        return exitValue;
    }
}

SwingWorkerWrapper.java

Wrapper以Swing方式使用上述类

package pkg2;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.List;

import javax.swing.SwingWorker;

public class SwingWorkerWrapper extends SwingWorker<Integer, Void> {
    private ProcessLauncher processLauncher;

    public SwingWorkerWrapper(List<String> commands) {
        processLauncher = new ProcessLauncher(commands);
        processLauncher.addPropertyChangeListener(new LauncherListener());
    }

    @Override
    protected Integer doInBackground() throws Exception {        
        return processLauncher.call();
    }

    private class LauncherListener implements PropertyChangeListener {
        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            firePropertyChange(evt.getPropertyName(), evt.getOldValue(), evt.getNewValue());
        }
    }
}

MainGui.java

使用上述SwingWorker的GUI类。运行此课程以获得整个节目。运行后,按&#34;启动流程&#34;该程序的按钮在单独的JVM中运行TestProgram。

package pkg2;

import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;

import javax.swing.*;
import javax.swing.border.Border;

@SuppressWarnings("serial")
public class MainGui extends JPanel {
    private static final String[] CMD_TEXT = {"java", "-cp"}; 
    private static final String TEST_PROGRAM = "pkg2.TestProgram";
    private JTextArea inputTextArea = new JTextArea(15, 30);
    private JTextArea errorTextArea = new JTextArea(15, 30);
    private List<String> commands = new ArrayList<>();

    public MainGui() {
        for (String cmd : CMD_TEXT) {
            commands.add(cmd);
        }
        String classpath = System.getProperty("java.class.path");
        commands.add(classpath);
        commands.add(TEST_PROGRAM);

        inputTextArea.setFocusable(false);
        JScrollPane inputScrollPane = new JScrollPane(inputTextArea);
        inputScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
        Border outsideBorder = BorderFactory.createTitledBorder("Input Messages");
        Border border = BorderFactory.createCompoundBorder(outsideBorder, inputScrollPane.getBorder());
        inputScrollPane.setBorder(border);

        errorTextArea.setFocusable(false);
        JScrollPane errorScrollPane = new JScrollPane(errorTextArea);
        errorScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
        outsideBorder = BorderFactory.createTitledBorder("Error Messages");
        border = BorderFactory.createCompoundBorder(outsideBorder, errorScrollPane.getBorder());
        errorScrollPane.setBorder(border);

        JPanel twoAreasPanel = new JPanel(new GridLayout(1, 0, 3, 3));
        twoAreasPanel.add(inputScrollPane);
        twoAreasPanel.add(errorScrollPane);

        JPanel btnPanel = new JPanel(new GridLayout(1, 0, 3, 3));
        btnPanel.add(new JButton(new LaunchProcessAction()));
        btnPanel.add(new JButton(new ExitAction()));

        setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
        setLayout(new BorderLayout(3, 3));
        add(twoAreasPanel, BorderLayout.CENTER);
        add(btnPanel, BorderLayout.PAGE_END);        
    }

    private class SwWrapperListener implements PropertyChangeListener {
        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
                SwingWorkerWrapper swW = (SwingWorkerWrapper) evt.getSource();
                try {
                    int exitCode = swW.get();
                    inputTextArea.append("Exit Code: " + exitCode + "\n");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    inputTextArea.append(e.getLocalizedMessage());
                    inputTextArea.append("\n");
                } catch (ExecutionException e) {
                    e.printStackTrace();
                    inputTextArea.append(e.getLocalizedMessage());
                    inputTextArea.append("\n");
                }
            } else if (GobblerType.OUTPUT.toString().equals(evt.getPropertyName())) {
                inputTextArea.append(evt.getNewValue() + "\n");
            } else if (GobblerType.ERROR.toString().equals(evt.getPropertyName())) {
                errorTextArea.append(evt.getNewValue() + "\n");
            }

        }
    }

    private class LaunchProcessAction extends MyAction {
        public LaunchProcessAction() {
            super("Launch Process", KeyEvent.VK_L);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            SwingWorkerWrapper swWrapper = new SwingWorkerWrapper(commands);
            swWrapper.addPropertyChangeListener(new SwWrapperListener());
            swWrapper.execute();
        }
    }

    private class ExitAction extends MyAction {
        public ExitAction() {
            super("Exit", KeyEvent.VK_X);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            System.exit(0);
        }
    }

    private static abstract class MyAction extends AbstractAction {
        public MyAction(String name, int mnemonic) {
            super(name);
            putValue(MNEMONIC_KEY, mnemonic);
        }
    }

    private static void createAndShowGui() {
        MainGui mainPanel = new MainGui();

        JFrame frame = new JFrame("Main GUI");
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.getContentPane().add(mainPanel);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> createAndShowGui());
    }
}

TestProgram.java

不要直接运行此程序,而是让主GUI运行此程序。确保编译此代码和所有代码

package pkg2;

import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;

import javax.swing.*;

@SuppressWarnings("serial")
public class TestProgram extends JPanel {
    private JTextField textField = new JTextField(20);
    private JSpinner exitCodeSpinner = new JSpinner(new SpinnerNumberModel(0, -10, 10, 1));

    public TestProgram() {        
        SendTextAction sendTextAxn = new SendTextAction();
        textField.setAction(sendTextAxn);

        JPanel panel1 = new JPanel();
        panel1.add(textField);
        panel1.add(new JButton(sendTextAxn));

        JPanel panel2 = new JPanel();
        panel2.add(new JLabel("Exit Code:"));
        panel2.add(exitCodeSpinner);
        panel2.add(new JButton(new ExitCodeAction()));
        panel2.add(new JButton(new ThrowExceptionAction()));

        setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
        add(panel1);
        add(panel2);
    }

    private static abstract class MyAction extends AbstractAction {
        public MyAction(String name, int mnemonic) {
            super(name);
            putValue(MNEMONIC_KEY, mnemonic);
        }

    }

    private class SendTextAction extends MyAction {
        public SendTextAction() {
            super("Send Text", KeyEvent.VK_S);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            String text = textField.getText();
            textField.setText("");
            System.out.println(text);
        }
    }

    private class ExitCodeAction extends MyAction {
        public ExitCodeAction() {
            super("Exit Code", KeyEvent.VK_X);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            int exitCode = (int) exitCodeSpinner.getValue();
            System.exit(exitCode);
        }
    }

    private class ThrowExceptionAction extends MyAction {
        public ThrowExceptionAction() {
            super("Throw Exception", KeyEvent.VK_T);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            // throw some unchecked exception
            throw new NumberFormatException("Unchecked exception thrown from within TestProgram");
        }
    }

    private static void createAndShowGui() {
        TestProgram mainPanel = new TestProgram();

        JFrame frame = new JFrame("Test Program");
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.getContentPane().add(mainPanel);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> createAndShowGui());
    }
}