java - SwingWorker 和命令行进程中断

标签 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 变量)添加关闭 Hook ,但当我关闭整个 GUI 时它会使用react。

最佳答案

您需要获取输入流和错误流,并在各自的线程中读取它们。现在你的错误流永远不会有机会,因为它前面有一个阻塞的 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

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

package pkg2;

public enum GobblerType {
    ERROR, OUTPUT
}

StreamGobbler2.java

stream 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;
    }

}

进程启动器.java

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

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

以 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());
        }
    }
}

主界面.java

使用上述 SwingWorker 的 GUI 类。运行这门课,让整个节目上路。运行后,按下此程序的“启动进程”按钮以在单独的 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());
    }
}

测试程序.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());
    }
}

关于java - SwingWorker 和命令行进程中断,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39131310/

相关文章:

java - 为什么 JFrame 无法正确添加组件

java - imageIcon/图像加载到框架中?

bash - Linux 命令在 shell 命令行中表现异常

command-line - 批处理文件: How to make * ignore ".svn" directories

java - 有没有可以直接执行MathType公式的编程语言?

java - 在java中验证mailto链接

java - 如何在android中将arraylist变量从一个类调用到另一个类

java - 计算切换按钮和

linux - 找到所有具有特定扩展名的文件然后执行

java - session 中的下拉选项