Выходные данные процесса становятся доступными только после завершения процесса

У меня есть Runnable, который считывает вывод консоли из исполняемого файла с внешним именем (см. Ниже) и записывает его как в файл журнала, так и в JTextArea.

Но мой Runnable не показывает вывод консоли в JTextArea, пока exe не завершится полностью. Как мне заставить его печатать вывод консоли, как это происходит?

Краткий пример кода ниже:

//Главный

import java.awt.*;
import java.io.IOException;
import javax.swing.*;

public class Example extends JFrame {

    private static final long serialVersionUID = 1L; 

    public static int maxX, maxY;

    public static JTextArea ta = new JTextArea(20, 60);//For LOG display window

    public static void main(String args[] ) throws IOException
    {   
        new Example();
    }

    public Example() {

        this.setTitle("Example");
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        //MAIN Panel
        final JPanel main = new JPanel();

        JButton RunButton = button.run(main);
        main.add(RunButton);

        Container container = getContentPane();


        container.add(main);
        this.pack();
        this.setVisible(true);
    }


}

//Прослушиватель действия кнопки

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.*;


public class button {

    public static JButton run( final JPanel parent ) {
        JButton RunButton = new JButton();      
        RunButton.setText("Start!");

        RunButton.addActionListener(
        new ActionListener()
        {
            public void actionPerformed( ActionEvent event)
            {
                try
                {

                    //Set up LOG Display    
                    JDialog dialog = new JDialog((JFrame)null, "Working...");
                    JPanel temp_panel = new JPanel();
                    temp_panel.add(new JScrollPane(Example.ta));
                    dialog.getContentPane().add(temp_panel);
                    dialog.pack();
                    dialog.setVisible(true);

                    //Build the Command
                    ArrayList<String> command = new ArrayList<String>();
                    command.add("ping");
                    command.add("127.0.0.1");

                    //Start the process
                    Process p = new ProcessBuilder(command).start();

                    //Starts LOG display capture in separate thread 
                    SwingUtilities.invokeLater(new execute(p));

                    //Wait for call to complete
                    p.waitFor();
                }
                catch(Exception err)
                {
                    JOptionPane.showMessageDialog( parent, "Error Executing Run!", "Warning", JOptionPane.ERROR_MESSAGE );  
                }

            }//end ActionPerformed
        });
        return RunButton;
    }
}

//Запускаемый

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class execute implements Runnable {  
    String line;
    Process p; 

    public execute ( Process process ) {
        p = process;
    }

    public void run() {
        try {
            //Read Process Stream Output and write to LOG file
            BufferedReader is = new BufferedReader( new InputStreamReader(p.getInputStream()));

            while ( (line = is.readLine()) != null ) {
                Example.ta.append(line + "\n");
            }   
            System.out.flush();     
        } catch(Exception ex) { ex.printStackTrace(); }  

    }
}

person JeffS    schedule 10.11.2011    source источник
comment
Вывод, вероятно, буферизируется ОС.   -  person biziclop    schedule 10.11.2011
comment
В опубликованном коде нет использования JTextArea ^)   -  person Victor Sorokin    schedule 10.11.2011
comment
Пожалуйста, перефразируйте тему: ваш Runnable действительно работает в отдельном потоке.   -  person alf    schedule 10.11.2011
comment
Что касается проблемы, что делает PartGen.ta.append(line + "\n")?   -  person alf    schedule 10.11.2011
comment
примечание: в классах Java всегда следует писать с заглавной буквы.   -  person Andrew    schedule 10.11.2011
comment
Что касается проблемы, что делает PartGen.ta.append(line + \n)? Java-файл PartGen определяет JTextArea ta. это все, что там происходит. вы можете заменить PartGen.ta на JTextArea ta.   -  person JeffS    schedule 10.11.2011
comment
Можете ли вы воспроизвести проблему вообще без Swing? То есть sscce.org будет полезен.   -  person alf    schedule 10.11.2011
comment
Собираем SSCCE прямо сейчас. Я опубликую его примерно через час или около того.   -  person JeffS    schedule 10.11.2011
comment
пожалуйста, изучите соглашения об именах Java и придерживайтесь их   -  person kleopatra    schedule 11.11.2011


Ответы (5)


Возможно, это потому, что вы не уважаете Swing политика многопоточности. Все обращения к компонентам Swing должны выполняться в потоке диспетчеризации событий. Таким образом, ваш runnable должен использовать SwingUtilities.invokeLater для обновления текстовой области в EDT, а не в вашем отдельном потоке.

РЕДАКТИРОВАТЬ: как упоминает Альф в своем комментарии: JTextArea.append является потокобезопасным, поэтому здесь это не обязательно. Тем не менее, я бы все равно сделал это, потому что, если бы добавление к текстовой области было заменено или дополнено любым другим взаимодействием Swing, оно больше не было бы потокобезопасным.

Также может случиться так, что внешний процесс не отправляет символ новой строки, из-за чего readLine блокируется до тех пор, пока он не будет найден или не будет достигнут конец связи.

person JB Nizet    schedule 10.11.2011
comment
К сожалению, вы ошибаетесь: public void append(String str)Этот метод является потокобезопасным, хотя большинство методов Swing — нет. Дополнительные сведения см. в разделе «Потоки и Swing». (согласно download.oracle.com/javase/6 /docs/api/javax/swing/) - person alf; 10.11.2011
comment
Бьюсь об заклад, проблема связана с буферизацией, но не могу этого доказать. Свинг здесь не при чем. - person alf; 10.11.2011
comment
@alf не верьте всему, что написано ;-) AFAIR, есть разумные сомнения в том, что методы textComponents, которые задокументированы как потокобезопасные, на самом деле являются таковыми. Лучше ошибиться на всякий случай и не полагаться на него - person kleopatra; 11.11.2011

Просто чтобы помочь майнеру - ниже полный минималистичный (опущено все не совсем необходимое) пример, который действительно работает в моем контексте: каждая строка отображается в текстовой области как прочитанная. Это в основном использует SwingWorker, как предложил Джастин, и немного переделал вещи для ясности.

public class ProcessExample {

    public static class ProcessWorker extends SwingWorker<Void, String> {
        private JTextArea ta;
        private List<String> process;

        public ProcessWorker(List<String> command, JTextArea ta) {
            this.process = command;
            this.ta = ta;
        }

        @Override
        protected Void doInBackground() throws Exception {
            Process p = new ProcessBuilder(process).start();
            // Read Process Stream Output and write to LOG file
            BufferedReader is = new BufferedReader(new InputStreamReader(
                    p.getInputStream()));
            String line;
            while ((line = is.readLine()) != null) {
                publish(line);
            }
            is.close();
            return null;
        }

        @Override
        protected void process(List<String> chunks) {
            for (String string : chunks) {
                ta.append(string + "\n");
            }
        }

    }

    private void startProcess(JTextArea ta) {
        ArrayList<String> command = new ArrayList<String>();
        command.add("ping");
        command.add("127.0.0.1");
        new ProcessWorker(command, ta).execute();
    }

    private JComponent getContent() {
        JPanel main = new JPanel(new BorderLayout());
        final JTextArea ta = new JTextArea(20, 60);
        main.add(new JScrollPane(ta));
        Action action = new AbstractAction("Start!") {

            @Override
            public void actionPerformed(ActionEvent e) {
                startProcess(ta);
            }
        };
        main.add(new JButton(action), BorderLayout.SOUTH);
        return main;
    }

    public static void main(String args[]) throws IOException {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame("Example");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

                frame.add(new ProcessExample().getContent());
                frame.pack();
                frame.setVisible(true);

            }
        });
    }

}
person kleopatra    schedule 18.11.2011

Вы можете попробовать ту же логику с SwingWorker. вместо. Вы можете расширить этот класс вместо реализации runnable. Это может взять вашу текстовую область в качестве параметра, и вы можете публиковать данные, не имея дело с SwingUtils.invokeLater, что проще...

Пытаться:

public class execute extends javax.swing.SwingWorker {  
      String line;
      Process p; 
      JTextArea jta;

      File f = new File( properties.prop.getProperty( "LOG_FILE_DIR" ) + "\\PartGen.log");

      public execute ( Process process , JTextArea jta ) {
          p = process;
          this.jta = jta;
      }

      //implements a method in the swingworker
      public void doInBackground() throws Exception {
        //Read Process Stream Output and write to LOG file
        BufferedReader is = new BufferedReader( new InputStreamReader(p.getInputStream()));

        while ( (line = is.readLine()) != null ) {
            osfile.writeline(line, f);
            publish(new String(line + "\n"));
        }   
        System.out.flush();  
        return null; 
  }
  //This will happen on the UI Thread.
  public void process(List lines){
      for(Object o : lines){
         jta.append((String)o);
      }
  }

  public void done(){
     try{
        get();
        //You will get here if everything was OK.  So show a popup or something to signal done.
     }catch(Exception ex){
         //this is where your IO Exception will surface, should you have one.
     }
  }
}

Кроме того, в вашем коде вызова, который, как я полагаю, находится где-то в вашем пользовательском интерфейсе:

    Process p = new ProcessBuilder(command).start();
    execute ex = new execute( p , yourTextArea);
    ex.execute();

Я не пытался это компилировать, поэтому вам, возможно, придется свериться с API, но, надеюсь, это даст вам представление о том, что делать.

person Justin Smith    schedule 10.11.2011
comment
изменено на Runnable run = new execute(p); SwingUtilities.invokeLater (новое выполнение (p)); и он все еще имеет те же результаты. - person JeffS; 10.11.2011
comment
Я тоже только что попробовал этот пример. Он не показывает вывод консоли, пока процесс не завершится. Та же проблема. - person JeffS; 10.11.2011
comment
Я протестировал этот код с помощью команды ping в Windows... которая работает. Это может быть так, как было предложено, и быть проблемой с буферизацией... - person Justin Smith; 11.11.2011
comment
Если это проблема с буферизацией в Windows, и она работает на вашем компьютере, для этого должна быть настройка. Я попробовал пример кода с пингом, который я разместил на компьютере с Windows 7 (64-разрядная версия) и Windows XP (32-разрядная версия), с теми же результатами. Я пытался возиться с буферизацией, но эта опция влияет только на историю команд. - person JeffS; 14.11.2011
comment
Позвольте мне уточнить. код работает в том смысле, что он печатает эхо-запросы в JTextArea, но только после завершения всех эхо-запросов. (т. е. программа завершена) Мне нужно, чтобы она распечатывала вывод, как это происходит. - person JeffS; 14.11.2011

Проблема заключалась не в том, что поток не собирал данные, а в том, что JTextArea просто не обновлялась. repaint(), revalidate() и updateUI() не обновили JTextArea, но сделали следующее:

Example.ta.update(Example.ta.getGraphics()); 
person JeffS    schedule 16.11.2011
comment
тогда что-то серьезно не так... это никогда не нужно, если все остальное в порядке - person kleopatra; 16.11.2011
comment
Kleopatra, SCCE, который я предоставил выше, работал на вас. Это не работало на моих машинах, пока я не добавил этот код. Если есть проблема с моим кодом, она будет присутствовать в SCCE выше. Тот факт, что у вас это работало без этого, говорит мне, что это связано с чем-то другим, а не с моим кодом. - person JeffS; 17.11.2011
comment
никогда не говорил, что у меня что-то работает (на самом деле, даже не пытался). Вы случайно пробовали API. Вы случайно наткнулись на что-то, что, кажется, работает для вас сейчас, но неправильно (на самом деле!) вызывать либо update(Graphics), либо getGraphics() в Swing. Если это кажется необходимым, настоящая ошибка в другом, вы просто размазываете по ней цвет вместо того, чтобы копаться, находить и исправлять основную проблему. Радуйтесь сейчас, ведь рано или поздно оно нанесет ответный удар ;-) - person kleopatra; 18.11.2011
comment
Ах хорошо. Тогда буду копать глубже. надевает шахтерскую шапку и хватает клетку с канарейкой - person JeffS; 18.11.2011

Проблема в этом случае связана с ожиданием:

p.waitFor();

Это заставляет прослушиватель действий кнопки ожидать в этой точке, пока процесс не будет завершен.

person Snicksie    schedule 23.03.2013