Процесс пакетного файла убит в Java: xcopy не закрывается

Кажется, это должно быть довольно легко решить, но я недостаточно знаком с использованием пакетных файлов, чтобы решить это самостоятельно. У меня есть метод Java, который создает построитель процессов и запускает пакетный файл в процессе. Пакетный файл использует команду xcopy для копирования одного каталога в другой. Пока пакетный файл выполняется в фоновом режиме, окно Java, содержащее JTextArea, отображает выходные данные процесса (копируемые каталоги). В окне также есть кнопка остановки, которая вызывает следующий код:

stopped = true;
backgroundTask.cancel(true);
backgroundTask.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();
    }
}

Теперь, чтобы запустить пакетный файл в фоновом режиме, я использую следующий код (изначально предложенный мне trashgod):

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. Кроме того, прошу извинить за любые оставленные там утверждения для отладки, которые я, возможно, забыл удалить.


person DerStrom8    schedule 21.12.2013    source источник


Ответы (3)


Одна из моих мыслей заключается в том, что неразумно ожидать, что удастся остановить этот общий процесс. Вы запускаете оболочку и передаете ей команду, затем останавливаете исходную программу — что она должна сделать, чтобы остановить копирование? Если бы вы были пользователем, использующим командную оболочку, вы могли бы ввести control-C, но я не знаю, существует ли программный эквивалент, доступный для Java, который будет делать то же самое.

person arcy    schedule 21.12.2013
comment
Это хороший момент. Я надеялся, что есть способ программно убить весь процесс из Java. Я думаю, мы увидим. - person DerStrom8; 21.12.2013

Есть несколько вещей, которые выделяются

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 Набрано напрямую, поэтому я не проверял)

person MadProgrammer    schedule 21.12.2013
comment
Хм, единственная проблема, с которой я столкнулся, заключается в том, что он частично заменяет несколько других методов, необходимых для остальной части программы. Возможно, мне придется опубликовать весь класс, чтобы найти способ заставить его работать без радикального изменения структуры всего этого. Я продолжу рассматривать этот пример, чтобы посмотреть, смогу ли я заставить его работать для моих целей. Весьма признателен. - person DerStrom8; 22.12.2013
comment
Итак, наиболее значительным изменением является чтение выходного символа за символом вместо использования BufferedReader... У меня были проблемы с этим в прошлом, поэтому избегайте этого. Остальное просто дает больше контроля над обработкой исключений;) - person MadProgrammer; 22.12.2013
comment
Ладно, думаю, теперь я понимаю. Но у меня есть новый вопрос: чтение вывода символ за символом занимает больше времени? Эта программа работает очень медленно, и я бы не хотел, чтобы она работала еще медленнее;) - person DerStrom8; 22.12.2013
comment
В этих условиях я бы сомневался, что это было бы заметно, если бы это произошло. Если читать InputStream по сети, это может быть... - person MadProgrammer; 22.12.2013
comment
Хорошо, тогда я посмотрю на это подробнее. Спасибо за ответы, и я дам вам знать, если что-то еще появится - person DerStrom8; 22.12.2013
comment
Ну, я, кажется, заставил его останавливать копирование при нажатии кнопки остановки, но проводник Windows по-прежнему значительно замедляется после его запуска. Если я запускаю его более одного раза и каждый раз останавливаю, проводник Windows все еще иногда дает сбой (все - панель задач, папки, рабочий стол и т. Д. Исчезает). - person DerStrom8; 22.12.2013
comment
В качестве дальнейшего примечания, я постоянно нажимаю уловку (IOException) из вашего кода каждый раз, когда останавливаю процесс. - person DerStrom8; 22.12.2013
comment
Какое исключение вы продолжаете получать ?? - person MadProgrammer; 22.12.2013
comment
java.io.IOException: поток закрыт на java.io.BufferedInputStream.getBufIfOpen(неизвестный источник) на java.io.BufferedInputStream.read(неизвестный источник) на diana.Progress$BackgroundTask.doInBackground(Progress.java:111) на diana. Progress$BackgroundTask.doInBackground(Progress.java:1) в javax.swing.SwingWorker$1.call(неизвестный источник) в java.util.concurrent.FutureTask$Sync.innerRun(неизвестный источник) в java.util.concurrent.FutureTask. run(неизвестный источник) в javax.swing.SwingWorker.run(неизвестный источник) в java.util.concurrent.ThreadPoolExecutor.runWorker(неизвестный источник) в - person DerStrom8; 22.12.2013
comment
Я играю с этим, если я использую файл bat, у меня нет конца проблемам, если я запускаю каталог команд xcopy через ProcessBuilder, он работает просто отлично... - person MadProgrammer; 22.12.2013
comment
К сожалению, у меня есть несколько вещей, которые нужно сделать с помощью пакетного файла, поэтому без переосмысления всего этого я не могу переключиться. Я могу просто удалить кнопку остановки из окна java, так как она отлично работает, когда ей разрешено закончить. У меня есть несколько предыдущих окон, которые спрашивают пользователя «Вы уверены?», поэтому я ожидаю, что все будет хорошо. Спасибо за помощь! - person DerStrom8; 22.12.2013

Вы не сказали, выполняет ли ваш пакетный файл какие-либо действия помимо вызова 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);
    }
}
person VGR    schedule 21.12.2013
comment
Забавно, что вы упомянули об этом, потому что я давно об этом думал. Однако я вложил много труда в пакетные файлы, и они используются для создания каталогов, а также для копирования файлов в них, поэтому я думаю, что для этой версии программы я хотел бы пока придерживаться их. Я уже задумался о новой версии, которая вообще не полагается на пакетные файлы в ближайшем будущем (как только я заработаю эту версию). В моей первой версии использовались ТОЛЬКО пакетные файлы, и на самом деле это версия 2. - person DerStrom8; 22.12.2013