【问题标题】:Batch file process killed in Java: xcopy does not close批处理文件进程在 Java 中被杀死:xcopy 不关闭
【发布时间】:2013-12-21 19:40:22
【问题描述】:

这似乎应该很容易解决,但是我对使用批处理文件自己解决它还不够熟悉。我有一个 Java 方法,它创建一个流程构建器并在流程中运行一个批处理文件。批处理文件使用 xcopy 命令将一个目录复制到另一个目录。当批处理文件在后台运行时,包含 JTextArea 的 Java 窗口会显示进程的输出(正在复制的目录)。窗口还有一个停止按钮,调用如下代码:

stopped = true;
backgroundTask.cancel(true);
backgroundTask.done();

done 方法如下所示:

protected void done() {
    statusLabel.setText((this.getState()).toString() + " " + status);
    stopButton.setEnabled(false);
    bar.setIndeterminate(false);
    if(stopped == false){
        JOptionPane.showMessageDialog(null, "Backup Complete.");
        closeWindow();
    }
    else if (stopped == true){
        JOptionPane.showMessageDialog(null, "Backup Cancelled.");
        closeWindow();
    }
}

现在,为了在后台运行批处理文件,我使用以下代码(最初是由垃圾神向我建议的):

protected Integer doInBackground() throws IOException {
    try {
        ProcessBuilder pb = new ProcessBuilder(commands);
        pb.redirectErrorStream(true);
        Process p = pb.start();
        String s;
        BufferedReader stdout = new BufferedReader(
        new InputStreamReader(p.getInputStream()));
        while ((s = stdout.readLine()) != null && !isCancelled()) {
            publish(s);
        }
        if (!isCancelled()) {
            status = p.waitFor();
        }
        p.getInputStream().close();
        p.getOutputStream().close();
        p.getErrorStream().close();
        p.destroy();
        closeWindow();
    } catch (IOException | InterruptedException ex) {
        ex.printStackTrace(System.err);
    }            
    return status;
}

我遇到的问题是:当我运行程序时,文件复制得很好,除非我按下前台窗口上的停止按钮。当我这样做时,它告诉我备份已被取消(应该如此),但它会留下三个额外的进程在运行,这些进程在任务管理器中可见:

我的猜测是第一个——“扩展复制实用程序”是罪魁祸首。由于它没有关闭,因此其他两个 cmd 进程仍在运行。然而,这是一个相当没有根据的猜测。

当我运行程序然后停止它时,Windows 资源管理器变得非常不稳定,有时冻结,有时完全崩溃。浏览文件夹(尤其是要复制到的目录)非常慢,而且即使在(假定)过程停止后,目录仍会继续被复制。我相信这是因为这些线永远达不到:

p.getInputStream().close();
p.getOutputStream().close();
p.getErrorStream().close();
p.destroy();

所以进程永远不会被杀死。我仍在研究一种在按下停止按钮时完全终止进程的方法,但如果有人有想法,我很乐意听到它们!

编辑

我选择发布整个课程,因为仅提供某些方法可能无法提供足够的信息。这是整个课程:

package diana;

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.Toolkit;
import java.awt.event.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;

import javax.swing.*;

@SuppressWarnings("serial")
public class Progress extends JFrame {
    public String[] commands;
    private final JLabel statusLabel = new JLabel("Status: ", JLabel.CENTER);
    private final JTextArea textArea = new JTextArea(20, 20);
    private JButton stopButton = new JButton("Stop");
    private JProgressBar bar = new JProgressBar();
    private BackgroundTask backgroundTask;
    private ProcessBuilder pb;
    private Process p;
    public boolean stopped = false;

    public void setCommands(String[] cmds) {
        commands = cmds;
    }
    private final ActionListener buttonActions = new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent ae) {
            JButton source = (JButton) ae.getSource();
            if (source == stopButton) {
                stopped = true;
                backgroundTask.cancel(true);
                backgroundTask.done();
            } else {
                backgroundTask = new BackgroundTask(commands);
            }
        }
    };

    private void displayGUI(String[] cmds) {
        commands = cmds;
        JFrame frame = new JFrame("Backup Progress");
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        JPanel panel = new JPanel();
        panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
        panel.setLayout(new BorderLayout(5, 5));
        JScrollPane sp = new JScrollPane();
        sp.setBorder(BorderFactory.createTitledBorder("Output: "));
        sp.setViewportView(textArea);
        textArea.setText(null);
        stopButton.setEnabled(true);
        backgroundTask = new BackgroundTask(commands);
        backgroundTask.execute();
        bar.setIndeterminate(true);
        stopButton.addActionListener(buttonActions);
        JPanel buttonPanel = new JPanel();
        buttonPanel.add(stopButton);
        buttonPanel.add(bar);
        panel.add(statusLabel, BorderLayout.PAGE_START);
        panel.add(sp, BorderLayout.CENTER);
        panel.add(buttonPanel, BorderLayout.PAGE_END);
        frame.setContentPane(panel);
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }

    /* Close current window */
    public void closeWindow() throws IOException {
        p.getInputStream().close();
        p.getOutputStream().close();
        p.getErrorStream().close();
        p.destroy();
        WindowEvent close = new WindowEvent(this, WindowEvent.WINDOW_CLOSING);
        Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(close);
        System.exit(0);
    }

    private class BackgroundTask extends SwingWorker<Integer, String> {
        private int status;
        public String[] commands;
        public BackgroundTask(String[] cmds) {
            commands = cmds;
            statusLabel.setText((this.getState()).toString());
        }

        @Override
        protected Integer doInBackground() throws IOException {
            try {
                pb = new ProcessBuilder(commands);
                pb.redirectErrorStream(true);
                p = pb.start();
                String s;
                BufferedReader stdout = new BufferedReader(
                    new InputStreamReader(p.getInputStream()));
                while ((s = stdout.readLine()) != null && !isCancelled()) {
                    publish(s);
                }
                if (!isCancelled()) {
                    status = p.waitFor();
                }
                closeWindow();
            } catch (IOException | InterruptedException ex) {
                ex.printStackTrace(System.err);
            }
            return status;
        }

        @Override
        protected void process(List<String> messages) {
            statusLabel.setText((this.getState()).toString());
            for (String message : messages) {
                textArea.append(message + "\n");
            }
        }

        @Override
        protected void done() {
            statusLabel.setText((this.getState()).toString() + " " + status);
            stopButton.setEnabled(false);
            bar.setIndeterminate(false);
            if (stopped == false) {
                JOptionPane.showMessageDialog(null, "Backup Complete.");
                try {
                    closeWindow();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            } else if (stopped == true) {
                JOptionPane.showMessageDialog(null, "Backup Cancelled.");
                try {
                    closeWindow();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public void run(String[] cmds) {
        commands = cmds;
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                new Progress().displayGUI(commands);
            }
        });
    }
}

再一次,我不能相信这段代码,因为它主要是由 SO 成员trashgod 提供的。另外,请原谅我可能忘记删除的任何用于调试的语句。

【问题讨论】:

    标签: java batch-file xcopy kill-process


    【解决方案1】:

    我的一个想法是,期望能够停止整个过程是不合理的。你正在启动一个 shell 并给它一个命令,然后停止原始程序——它应该做什么来停止复制?如果您是使用命令 shell 的用户,您可以输入 control-C,但我不知道是否有可用于 Java 的编程等效项,它会做同样的事情。

    【讨论】:

    • 这是一个很好的观点。我希望有一种方法可以从 Java 中以编程方式实际杀死整个过程。我想我们会看到的。
    【解决方案2】:

    有几件事很突出

    BufferedReader#readLine是阻塞方式,可能不会响应当前线程的中断标志(和解除阻塞)

    您已将整个逻辑包围在单个 try-catch 阻塞中,这意味着如果抛出 InterruptedException,您将跳过您试图用于处理进程的整个代码部分。

    这样的方法可能稍微好一点。 InputStream#read 仍然是你的致命弱点,但因为我现在在尝试阅读之前检查 isCancelled,所以不太可能引起很多问题

    InputStream is = null;
    Process p = null;
    try {
        ProcessBuilder pb = new ProcessBuilder(commands);
        pb.redirectErrorStream(true);
        p = pb.start();
    
        StringBuilder sb = new StringBuilder(128);
        is = p.getInputStream();
        int in = -1;
        while (!isCancelled() && (in = is.read()) != -1) {
            sb.append((char)in));
            if (((char)in) == '\n') {
                publish(sb.toString());
                sb.delete(0, sb.length());
            }
        }
        if (!isCancelled()) {
            status = p.waitFor();
        } else {
            p.destroy();
        }
    } catch (IOException ex) {
        ex.printStackTrace(System.err);
    } catch (InterruptedException ex) {
        ex.printStackTrace(System.err);
        try {
            p.destroy();
        } catch (Exception exp) {
        }
    } finally {
        try {
            is.close();
        } catch (Exception exp) {
        }
        // Make sure you are re-syncing this to the EDT first...
        closeWindow();
    }
    

    (nb直接打字,所以没测试过)

    【讨论】:

    • 嗯,我唯一遇到的问题是它部分取代了程序其余部分所需的其他几种方法。也许我将不得不发布整个课程,以便找到一种方法来使其工作而不会彻底改变整个事情的结构。我会继续查看这个例子,看看我是否可以让它为我的目的工作。非常感谢。
    • 所以,最重要的变化是逐个字符地读取输出字符,而不是使用 BufferedReader...我过去遇到过这个问题,所以避免这样做。其余的只是对异常处理进行更多控制;)
    • 好的,我想我现在明白了。但我有一个新问题:逐个字符读取输出是否需要更长的时间?这个程序运行得很慢,我宁愿它不要再慢了;)
    • 在这些情况下,我怀疑它是否会引起注意。如果通过网络读取 InputStream 可能是...
    • 好的,我会进一步研究。感谢您的回复,如果有其他问题,我会通知您
    【解决方案3】:

    您没有说明您的批处理文件是否执行除了调用 xcopy 之外的任何操作。如果没有,您可能需要考虑使用 Java 来进行文件复制,而不是运行外部进程。中断自己的代码比停止外部进程容易得多:

    static void copyTree(final Path source, final Path destination)
    throws IOException {
        if (Files.isDirectory(source)) {
            Files.walkFileTree(source, new SimpleFileVisitor<Path>()
            {
                @Override
                public FileVisitResult preVisitDirectory(Path dir,
                                             BasicFileAttributes attributes)
                throws IOException {
                    if (Thread.interrupted()) {
                        throw new InterruptedIOException();
                    }
    
                    Path destinationDir =
                        destination.resolve(source.relativize(dir));
                    Files.createDirectories(destinationDir);
    
                    BasicFileAttributeView view =
                        Files.getFileAttributeView(destinationDir,
                            BasicFileAttributeView.class);
                    view.setTimes(
                        attributes.lastModifiedTime(),
                        attributes.lastAccessTime(),
                        attributes.creationTime());
    
                    return FileVisitResult.CONTINUE;
                }
    
                @Override
                public FileVisitResult visitFile(Path file,
                                             BasicFileAttributes attributes)
                throws IOException {
                    if (Thread.interrupted()) {
                        throw new InterruptedIOException();
                    }
    
                    Files.copy(file,
                        destination.resolve(source.relativize(file)),
                        StandardCopyOption.COPY_ATTRIBUTES,
                        LinkOption.NOFOLLOW_LINKS);
    
                    return FileVisitResult.CONTINUE;
                }
            });
        } else {
            Files.copy(source, destination,
                StandardCopyOption.COPY_ATTRIBUTES,
                LinkOption.NOFOLLOW_LINKS);
        }
    }
    

    【讨论】:

    • 有趣的是你应该提到这一点,因为这是我一直在考虑的事情。然而,我在批处理文件中投入了大量工作,它们用于创建目录以及将文件复制到它们,所以我认为对于这个版本的程序,我现在想坚持使用它们。我已经考虑在不久的将来完全不依赖批处理文件的新版本(一旦我得到这个版本工作)。我的第一个版本只使用了批处理文件,这实际上是第 2 版。
    猜你喜欢
    • 2018-05-18
    • 2017-08-13
    • 1970-01-01
    • 1970-01-01
    • 2012-10-22
    • 2021-12-05
    • 1970-01-01
    • 1970-01-01
    • 2014-01-20
    相关资源
    最近更新 更多