Ausgaben von ProcessBuilder Prozess "streamen"

ralfb1105

Bekanntes Mitglied
Hallo,

ich habe ein Problem/eine Frage zu den Ausgaben die "ProcessBuilder" liefert. Ich möchte mir ein Java Programm schreiben was verschiedene Backup Aufgaben auf meinem Mac erledigt. Hierzu verwende ich ProcessBuilder um ein Shell Script auszuführen welches dann mittels rsync Daten synchronisiert/sichert. JA - ich weiß, dadurch verliere ich die Plattformunabhängigkeit des Java Programms, aber das ist in meinem Fall so gewollt :rolleyes:

Hier mal ein kleines Programm was da Ganze testweise auf ein Minimum runter bricht:

Java:
package gui;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;

public class Test1 {

    public static void main(String[] args) {
        ProcessBuilder pb = new ProcessBuilder( "cmd", "/c", "dir", "/s" );
        pb.directory( new File("f:/") );
       
        try {
            Process p = pb.start();
            BufferedReader reader =
            new BufferedReader(new InputStreamReader(p.getInputStream()));
            StringBuilder builder = new StringBuilder();
            String line = null;
            while ( (line = reader.readLine()) != null) {
               builder.append(line);
               builder.append(System.getProperty("line.separator"));
            }
            String result = builder.toString();
            System.out.println(result);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Das Programm funktioniert, hat aber leider das unschöne Verhalten das die Ausgaben erst dann kommen wenn der process beendet wird. Bei einem Process der nicht so viel macht kein Problem, doch bei meinen Backup Scripts führt das dazu das ich erst einmal 15 Minuten nichts sehe und dann kommen die Ausgaben die der rsync produziert :(

Bei dem obigen Beispiel kann man das auch nachvollziehen wenn man ein Verzeicchnis nimmt das eine größere Struktur hat.

Herausforderung:

Ich bin auf der Suche nach einer Möglichkeit das die Ausgaben die mein Process produziert sofort ausgegeben werden, quasi als "append".

Hat jemand eine Idee wie man so etwas bezogen auf das obige Beispiel machen könnte?

Danke im Voraus!

Gruß

Ralf
 

Tarrew

Top Contributor
Das liegt daran, dass deine while-Schleife so lange läuft, bis der Prozess beendet ist und du erst danach die Ausgabe rausschreibst.

Wenn du innerhalb der Schleife die Ausgabe machst, dann siehst du es direkt:
Java:
public class Test1 {

    public static void main(String[] args) {
        ProcessBuilder pb = new ProcessBuilder("cmd", "/c", "dir", "/s");
        pb.directory(new File("D:/"));

        try {
            Process p = pb.start();
            BufferedReader reader =
                    new BufferedReader(new InputStreamReader(p.getInputStream()));
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
 

ralfb1105

Bekanntes Mitglied
Hallo Tarrew,

Vielen Dank - manchmal ist man wirklich wie vernagelt :confused: so geht es!
Wünsche Dir einen schönen Abend.

Gruß

Ralf
 

Tarrew

Top Contributor
Kürzer gehts auch mit Sachen wie:
Java:
public class Test1 {

    public static void main(String[] args) throws IOException, InterruptedException {
        Process pb = new ProcessBuilder("cmd", "/c", "dir", "/s").directory(new File("D:/")).inheritIO().start();
        pb.waitFor();
    }
}

Dir auch ;)
 

ralfb1105

Bekanntes Mitglied
Hallo Tarrew,

wow, fast in einer Zeile ... als blutiger Anfänger bin ich echt beeindruckt - werde ich mal probieren.
Ich hoffe ich darf noch eine weitere Frage stellen, auch wenn es jetzt darum geht die Ausgabe nicht auf die Konsole auszugeben, sonder in ein jTextArea. Ich habe folgenden Test Code erstellt und habe dabei wieder das Verhalten das die Ausgabe erst nach dem Ende des Processes im Text Feld erscheint. Ich habe es mit jTextArea.setText und jTextArea.append probiert - gleiches Ergebnis .. ??

Hier mal der vollständige Code:

Java:
package gui;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

import javax.swing.WindowConstants;
import javax.swing.SwingUtilities;


/**
* This code was edited or generated using CloudGarden's Jigloo
* SWT/Swing GUI Builder, which is free for non-commercial
* use. If Jigloo is being used commercially (ie, by a corporation,
* company or business for any purpose whatever) then you
* should purchase a license for each developer using Jigloo.
* Please visit www.cloudgarden.com for details.
* Use of Jigloo implies acceptance of these licensing terms.
* A COMMERCIAL LICENSE HAS NOT BEEN PURCHASED FOR
* THIS MACHINE, SO JIGLOO OR THIS CODE CANNOT BE USED
* LEGALLY FOR ANY CORPORATE OR COMMERCIAL PURPOSE.
*/
public class ProcessBuilderGUI extends javax.swing.JFrame {
    private JTextArea jTConsoleOutput;
    private JScrollPane jScrollPane1;
    private JButton JBClearConsole;
    private JButton JBStart;
    private JButton JBExit;
    private JLabel jLHeaderText;

    /**
    * Auto-generated main method to display this JFrame
    */
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                ProcessBuilderGUI inst = new ProcessBuilderGUI();
                inst.setLocationRelativeTo(null);
                inst.setVisible(true);
            }
        });
    }
   
    public ProcessBuilderGUI() {
        super();
        initGUI();
    }
   
    private void initGUI() {
        try {
            setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
            getContentPane().setLayout(null);
            {
                jScrollPane1 = new JScrollPane();
                getContentPane().add(jScrollPane1);
                jScrollPane1.setBounds(38, 48, 606, 396);
                {
                    jTConsoleOutput = new JTextArea();
                    jScrollPane1.setViewportView(jTConsoleOutput);
                    jTConsoleOutput.setBounds(38, 48, 606, 396);
                    jTConsoleOutput.setEditable(false);
                    jTConsoleOutput.setBackground(new java.awt.Color(255,255,236));
                }
            }
            {
                jLHeaderText = new JLabel();
                getContentPane().add(jLHeaderText);
                jLHeaderText.setText("  Command Output Console");
                jLHeaderText.setBounds(38, 19, 605, 29);
                jLHeaderText.setFont(new java.awt.Font("Segoe UI",1,16));
                jLHeaderText.setForeground(new java.awt.Color(255,0,0));
            }
            {
                JBExit = new JButton();
                getContentPane().add(JBExit);
                JBExit.setText("Exit");
                JBExit.setBounds(661, 410, 87, 34);
                JBExit.setFont(new java.awt.Font("Segoe UI",0,16));
                JBExit.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent evt) {
                        JBExitActionPerformed(evt);
                    }
                });
            }
            {
                JBStart = new JButton();
                getContentPane().add(JBStart);
                JBStart.setText("Start");
                JBStart.setBounds(661, 333, 87, 38);
                JBStart.setFont(new java.awt.Font("Segoe UI",0,16));
                JBStart.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent evt) {
                        JBStartActionPerformed(evt);
                    }
                });
            }
            {
                JBClearConsole = new JButton();
                getContentPane().add(JBClearConsole);
                JBClearConsole.setText("Clear Console");
                JBClearConsole.setBounds(38, 463, 138, 36);
                JBClearConsole.setFont(new java.awt.Font("Segoe UI",0,16));
                JBClearConsole.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent evt) {
                        JBClearConsoleActionPerformed(evt);
                    }
                });
            }
            pack();
            this.setSize(823, 561);
        } catch (Exception e) {
            //add your error handling code here
            e.printStackTrace();
        }
    }
   
    private void JBExitActionPerformed(ActionEvent evt) {
        System.exit(0);
    }
   
    private void JBClearConsoleActionPerformed(ActionEvent evt) {
        jTConsoleOutput.setText("");
    }
   
    private void JBStartActionPerformed(ActionEvent evt) {
        runCommand();
    }
   
    private void runCommand() {
        ProcessBuilder pb = new ProcessBuilder( "/Users/ralfb/Progs/test.sh" );

        try {
            Process p = pb.start();
            BufferedReader reader =
            new BufferedReader(new InputStreamReader(p.getInputStream()));
            StringBuilder builder = new StringBuilder();
            String line = null;
            while ( (line = reader.readLine()) != null) {
               builder.append(line);
               builder.append(System.getProperty("line.separator"));
               String result = builder.toString();
               jTConsoleOutput.setText(result);
               //jTConsoleOutput.append(result);
               //System.out.println(result);
            }
           
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Hättest Du oder jemand anders eine Idee was ich hier wieder falsch mache? Wie gesagt, die Ausgabe auf der Console kommt sofort!

Danke schon mal für Eure Unterstützung.

Gruß

Ralf
 

ralfb1105

Bekanntes Mitglied
Hallo,

ein Fehler war schon mal das mit jTextArea.setText der text immer wider überschrieben wird. Mit folgender Zeile wird der text nun nicht mehr überschrieben, aber trotzdem kommt die Ausgabe nicht nach jeder Ausgabe des Shell Scripts :(

Java:
jTConsoleOutput.setText(line + "\n");

Bin für jede Idee dankbar!

Gruß

Ralf
 

X5-599

Top Contributor
Hallo,

Es liegt einfach daran, dass du den EDT (Event Dispatch Thread) blockierst. Alles was zb in actionPerformed() passiert, wird vom EDT bearbeitet. In der Regel sollten das nur sehr schnelle Operationen sein, die die GUI Komponenten ändern (einSwingComponent.setText("text"); o.ä.). Denn der EDT sorgt u.a. für die Darstellung und Aktualisierung der gesamten GUI.

Du müsstest hier einen separaten Thread starten. Sofort nach diesem Starten beendet sich die actionPerformed() Methode und der EDT ist wieder frei und kann sich weiter um die GUI kümmern. Dieser Thread würde dann das Auslesen des Process Outputs bearbeiten.
 

ralfb1105

Bekanntes Mitglied
Hallo,

erst einmal vielen dank für Deine Analyse. Da ich ein Java Neuling bin muss ich zugeben das ich vom EDT noch nie gehört habe o_O und ich im Moment auch nicht ganz sicher bin wo ich im meinem Code (s.o.) genau den hebel ansetzen muss.

Wenn Du schreibst

Du müsstest hier einen separaten Thread starten. ..... Dieser Thread würde dann das Auslesen des Process Outputs bearbeiten

heisst das auf mein Code Beispiel bezogen das ich die komplette Methode
Java:
private void runCommand() {
        ProcessBuilder pb = new ProcessBuilder( "/Users/ralfb/Progs/test.sh" );

        try {
            Process p = pb.start();
            BufferedReader reader =
            new BufferedReader(new InputStreamReader(p.getInputStream()));
            StringBuilder builder = new StringBuilder();
            String line = null;
            while ( (line = reader.readLine()) != null) {
               builder.append(line);
               builder.append(System.getProperty("line.separator"));
               String result = builder.toString();
               jTConsoleOutput.setText(result);
               //jTConsoleOutput.append(result);
               //System.out.println(result);
            }
          
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

in einen separaten Thread packen muss?
Wenn Du mir hierzu an Hand meines Beispiels auf die Sprünge helfen könntest wäre ich sehr dankbar, werde natürlich parallel ein bisschen rum probieren ;-)

Danke nochmals!

Gruß

Ralf
 

ralfb1105

Bekanntes Mitglied
Hallo,

ich habe folgendes implementiert und es scheint zu funktionieren, wie ein erster kurzer Test gezeigt hat:
Java:
    class runCommand implements Runnable {

        @Override
        public void run() {
            // TODO Auto-generated method stub
            ProcessBuilder pb = new ProcessBuilder( "cmd", "/c", "dir", "/s" );
            pb.directory( new File("f:/") );
            //Process p = null;
            try {
                Process p = pb.start();
                BufferedReader reader =
                new BufferedReader(new InputStreamReader(p.getInputStream()));
                StringBuilder builder = new StringBuilder();
                String line = null;
                while ( (line = reader.readLine()) != null) {
                   builder.append(line);
                   builder.append(System.getProperty("line.separator"));
                   String result = builder.toString();
                   jTConsoleOutput.setText(result);
                   //System.out.println(result);
                }
               
            } catch (IOException e) {
                e.printStackTrace();
            }

        }

    }

    private void JBStartActionPerformed(ActionEvent evt) {
        Runnable runCmd = new runCommand();
        Thread thread1 = new Thread(runCmd);
        thread1.start();
    }

Ist das in etwas so wie Du es Dir vorgestellt hat oder ist diese Implementierung schlecht?

Gruß

Ralf
 

ralfb1105

Bekanntes Mitglied
Hallo Klaus,

Danke für die Links, hatte ich gerade erst gesehen, ist aber dann in etwa so wie ich es gemacht habe.

Gruß

Ralf
 

X5-599

Top Contributor
Also generell gesagt: Ja, es ist richtig. Es funktioniert ja auch wie gewollt.

Allerdings wird eine Besonderheit von Swing nicht beachtet. Swing ist nicht Thread Sicher. Daraus ergibt sich folgende Konvention: Mann sollte aus einem Thread heraus keine GUI Komponenten manipulieren. Bei einem einzigen Thread mag das noch kein Problem sein. Bei mehreren Threads aber, die auf die selben GUI Komponenten zugreifen und diese manipulieren, kann das zu unerwarteten Effekten führen. Wie diese aussehen? Das weiss ich auch nicht genau. Ich habe das immer als Gegeben hingenommen. GUI Manipulation sollte nur auf dem EDT erfolgen.

Das erreicht man z.B. mittels SwingUtilities.invokeLater() / invokeAndWait() Methoden. Die erwarten ein Runnable Objekt (genau wie ein Thread). Dessen run Methode wird so (durch SwingUtilities) auf dem EDT ausgeführt.

Code:
Runnable append = new Runnable() {
	
	@Override
	public void run()
	{
		jTConsoleOutput.setText(result);
	}
};

SwingUtilities.invokeLater(append);

Diesen Code müsstes du für die Zeile: jTConsoleOutput.setText(result); einsetzen.
 

ralfb1105

Bekanntes Mitglied
Hallo,

ich habe jetzt mal probiert Deinen nützlichen Tip in den Code folgendermaßen zu integrieren:
Java:
class runCommand implements Runnable {

        @Override
        public void run() {
            // TODO Auto-generated method stub
            ProcessBuilder pb = new ProcessBuilder( "cmd", "/c", "dir", "/s" );
            pb.directory( new File("f:/") );
            //Process p = null;
            try {
                Process p = pb.start();
                BufferedReader reader =
                new BufferedReader(new InputStreamReader(p.getInputStream()));
                StringBuilder builder = new StringBuilder();
                String line = null;
                while ( (line = reader.readLine()) != null) {
                   builder.append(line);
                   builder.append(System.getProperty("line.separator"));
                   String result = builder.toString();
                   Runnable append = new Runnable() {
                        @Override
                        public void run()
                        {
                            jTConsoleOutput.setText(result);
                        }
                    };
                    SwingUtilities.invokeLater(append);
                   //jTConsoleOutput.setText(result);
                   //System.out.println(result);
                }
               
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
In Eclipse bekomme ich dann aber einen Fehler angezeigt:
"Cannot refer to the non-final local variable result defined in an enclosing scope."
Der Vorschlag von Eclipse ist folgender:
Code:
final String result = builder.toString();
Wenn ich das mache ist Eclipse zufrieden, starte ich aber nun den Code kommt es zu einer OutOfMemoryError Exception:
Code:
Exception in thread "Thread-2" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOfRange(Arrays.java:2694)
    at java.lang.String.<init>(String.java:203)
    at java.lang.StringBuilder.toString(StringBuilder.java:405)
    at gui.ProcessBuilderGUI$runCommand.run(ProcessBuilderGUI.java:76)
    at java.lang.Thread.run(Thread.java:745)
Das Programm wird aber ausgeführt und der Ouput des Kommandos aus ProcessBuilder wird in der JTextArea angezeigt.

Liegt das nun an dem "final" oder generell an dem 2. Thread?

Gruß

Ralf
 

X5-599

Top Contributor
Also bei mir gibt es keinen Fehler wegen non-final Variable ... Wundert mich ehrlichgesagt auch; hatte eigentlich damit gerechnet. Der Vorschlag von Eclipse ist sicherlich richtig. Einfach das Schlüsselwort "final" voranstellen.
Den OutOfMemory Fehler kann ich mir nicht erklären. Der StringBuilder erzeugt zwar jedesmal intern einen neuen String, aber das sollte doch kein Problem sein...

Wie "final" Variablen so in der JVM behandelt werden, weiss ich leider nicht. Schon möglich, dass die erst viel später von Garbage Collector aufgeräumt werden und so der Heap Speicher irgendwann nicht mehr ausreicht. Vielleicht weiss das ein anderer hier?

Du könntest noch folgendes testen. Anstatt jedesmal per .setText() den gesamten String der TextArea hinzuzufügen, könntest du es nochmal mit .append() versuchen:

Code:
final String result = line;
Runnable append = new Runnable() {
  @Override
  public void run()
  {
    jTConsoleOutput.append(result);
    jTConsoleOutput.append(System.getProperty("line.separator"));
  }
};
 

ralfb1105

Bekanntes Mitglied
Hallo,
das mit dem OutOfMemory Error muss wirklich an zu vielen nicht aufgeräumten Variablen etc. im Heap Speicher liegen. Ich bekomme das auch nur wenn ich sehr viele und schnelle Ausgaben produziere. Nehme ich z.B. das "/s" (Rekursiv) weg läuft es problemlos - mit dem "final".

Das mit dem "jTConsoleOutput.append(result);" funktioniert bei mir nicht so wie gewollt, oder ich mache was falsch ...
Wenn ich z.B. mal testweise, auf meinem MAC, folgendes Shell Script ablaufen lasse:
Code:
#!/bin/bash
for i in 1 2 3 4 5 6 7 8 9
do
   echo "Round: $i: TEXT TEXT TEXT TEXT TEXT TEXT"
   sleep 2
done
Code:
#!/bin/bash
for i in 1 2 3 4 5 6 7 8 9
do
   echo "Round$i: TEXT TEXT TEXT TEXT TEXT TEXT"
   sleep 2
done

bekomme ich folgende Ausgaben:

Round1: TEXT TEXT TEXT TEXT TEXT TEXT
Round1: TEXT TEXT TEXT TEXT TEXT TEXT
Round2: TEXT TEXT TEXT TEXT TEXT TEXT
Round1: TEXT TEXT TEXT TEXT TEXT TEXT
Round2: TEXT TEXT TEXT TEXT TEXT TEXT
Round3: TEXT TEXT TEXT TEXT TEXT TEXT
Round1: TEXT TEXT TEXT TEXT TEXT TEXT
Round2: TEXT TEXT TEXT TEXT TEXT TEXT
Round3: TEXT TEXT TEXT TEXT TEXT TEXT
Round4: TEXT TEXT TEXT TEXT TEXT TEXT
...
usw.
Mit "jTConsoleOutput.setText(result);" funktioniert es wie gewünscht:

Round1: TEXT TEXT TEXT TEXT TEXT TEXT
Round2: TEXT TEXT TEXT TEXT TEXT TEXT
Round3: TEXT TEXT TEXT TEXT TEXT TEXT
Round4: TEXT TEXT TEXT TEXT TEXT TEXT
Round5: TEXT TEXT TEXT TEXT TEXT TEXT
Round6: TEXT TEXT TEXT TEXT TEXT TEXT
Round7: TEXT TEXT TEXT TEXT TEXT TEXT
Round8: TEXT TEXT TEXT TEXT TEXT TEXT
Round9: EXT TEXT TEXT TEXT TEXT TEXT

Das mit dem OutOfMemoryError hat schon einen "bitteren Nachgeschmack", auch wenn es mit den Scripts die ich benötige diesen Fehler nicht gibt. Probiere ein "ls -alR $HOME" auf dem MAC gibt es auch den Memory Error.

Vielleicht hat ja noch einer hier im Forum eine Idee dazu, generell habe ich jetzt eine Version die funktioniert, wenn auch wahrscheinlich nicht in aller Konsequenz ideal.

Danke aber für Deine tolle Unterstützung und die nützlichen Tips!

Gruß

Ralf
 

mrBrown

Super-Moderator
Mitarbeiter
Also bei mir gibt es keinen Fehler wegen non-final Variable ... Wundert mich ehrlichgesagt auch; hatte eigentlich damit gerechnet.
Die ist "effectively final", seit Java 8 geht das auch ohne explizites final.

Wie "final" Variablen so in der JVM behandelt werden, weiss ich leider nicht. Schon möglich, dass die erst viel später von Garbage Collector aufgeräumt werden und so der Heap Speicher irgendwann nicht mehr ausreicht. Vielleicht weiss das ein anderer hier?
Das lokale final hat keinen Einfluss darauf. Vor einem OOME würde auch alles aufgeräumt werden, was aufgeräumt werden kann.

das mit dem OutOfMemory Error muss wirklich an zu vielen nicht aufgeräumten Variablen etc. im Heap Speicher liegen. Ich bekomme das auch nur wenn ich sehr viele und schnelle Ausgaben produziere. Nehme ich z.B. das "/s" (Rekursiv) weg läuft es problemlos - mit dem "final".
Lass dir einfach mal die Länge des Strings ausgeben, ich würde da die Fehlerquelle vermuten
 

ralfb1105

Bekanntes Mitglied
Hallo mrBrown,

hier mal die Werte:

Ohne /s - ohne OOM:

Length: 52741
Capacity: 92158

mit /s und mit OOM:

Length: 110954
Capacity: 184318
Exception in thread "Thread-2" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOfRange(Arrays.java:2694)
at java.lang.String.<init>(String.java:203)
at java.lang.StringBuilder.toString(StringBuilder.java:405)
at gui.ProcessBuilderGUI$runCommand.run(ProcessBuilderGUI.java:77)
at java.lang.Thread.run(Thread.java:745)

Komme ich hier an Grenzen von internen Puffern oder muss ich bei so etwas den Heap Speicher vergrößern ??

Ach ja, das mit "final" ist verständlich, ich bin noch auf Java SE7 und somit muss ich ex explizit angeben!

Danke für Deine Hilfe!

Gruß

Ralf
 

X5-599

Top Contributor
Merkwürdig. Ehrlich gesagt verstehe ich nicht wirklich wie final Strings in solchen Konstrukten, wie ich es gepostet habe, von Operationen von denen man nicht weiss wann sie passieren, adressiert werden.

Code:
final String result = line;
Runnable append = new Runnable() {
  @Override
  public void run()
  {
    jTConsoleOutput.append(result);
    jTConsoleOutput.append(System.getProperty("line.separator"));
  }
};

Ich hatte halt immer angenommen, dass durch das final das erzeugte Runnable auch dann noch auf die Variable zugreifen kann wenn z.B. dieser Code (in einer Schleife) ein zweites Mal ausgeführt wird. Eine normale Variable würde ja überschrieben. Wenn aber das erste Runnable noch nicht ausgeführt wurde... worauf verweist dann die Variable, wenn dessen run() Methode irgendwann mal ausgeführt wird?

Ich hatte gehofft, dass es so ähnlich läuft wie bei einer Variablenübergabe an eine Methode. Dort hin werden ja nur Kopien der Referenzen übergeben. So dass ein neues Zuweisen (innerhalb der Methode) keine Auswirkungen auf die original Referenz hat.

Irgendwo scheint da noch was zu passieren, was ich noch nicht ergründet habe. Man lernt also nie aus :)

Ok. Es gibt auch noch andere Wege. Meine Hoffnung war nebenläufige Arbeit und GUI Manipulation am besten mit Elementaren Mitteln erklären zu können... Swing bringt allerdings auch einen eigenen Mechanismus mit um so was zu erledigen. Das nimmt dem Programmieren eine Menge Arbeit ab; verdeckt aber auch gleichzeit einiges von dem was "hinter den Kulissen" passiert.

Code:
class ProcessWorker extends SwingWorker<Void, String>
{
	private JTextArea jTConsoleOutput;
	
	public ProcessWorker(JTextArea textArea)
	{
		this.jTConsoleOutput = textArea;
	}
	
	
	@Override
	protected Void doInBackground() throws Exception
	{
		//hier läuft alles in einem eigenen Thread (nebenläufig) ab. Es ist also egal wie lange die Abarbeitung hier dauert.
		BufferedReader reader = null;
		
		try
		{
			ProcessBuilder pb = new ProcessBuilder("/Users/ralfb/Progs/test.sh");
			Process p = pb.start();
			reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
			
			String line = null;
			while((line = reader.readLine()) != null)
			{
				// dieser Aufruf führt (über Umwege) dazu, dass die untere process(List<String>) Methode aufgerufen wird.
				publish(line);
			}
		}
		finally
		{
			if(reader != null)
			{
				reader.close();
			}
		}
		
		return null;
	}
	
	@Override
	protected void process(List<String> chunks)
	{
		//hier läuft alles auf dem EDT (Event Dispatch Thread). Darum können (und sollten) wir hier die GUI Komponenten manipulieren.
		for(int i = 0; i < chunks.size(); i++)
		{
			jTConsoleOutput.append(chunks.get(i));
			jTConsoleOutput.append(System.getProperty("line.separator"));
		}
	}
}

Aufrufen würdest du das dann in einem ActionListener:
Code:
einButton.addActionListener(new ActionListener() {
	@Override
	public void actionPerformed(ActionEvent e)
	{
		ProcessWorker p = new ProcessWorker(jTConsoleOutput);
		p.execute();
	}
});

Nur wichtig, dass die JTextArea, die dem neuen ProcessWorker übergeben wurde bereits initialisiert wurde. Noch etwas: Diese SwingWorker sind so gedacht, dass sie nur ein einziges Mal per execute() gestartet werden sollen. Soll der SwingWorker ein weiteres Mal laufen, so muss vorher ein neues Objekt erzeugt werden.
 

mrBrown

Super-Moderator
Mitarbeiter
Merkwürdig. Ehrlich gesagt verstehe ich nicht wirklich wie final Strings in solchen Konstrukten, wie ich es gepostet habe, von Operationen von denen man nicht weiss wann sie passieren, adressiert werden.

Code:
final String result = line;
Runnable append = new Runnable() {
  @Override
  public void run()
  {
    jTConsoleOutput.append(result);
    jTConsoleOutput.append(System.getProperty("line.separator"));
  }
};

Ich hatte halt immer angenommen, dass durch das final das erzeugte Runnable auch dann noch auf die Variable zugreifen kann wenn z.B. dieser Code (in einer Schleife) ein zweites Mal ausgeführt wird. Eine normale Variable würde ja überschrieben. Wenn aber das erste Runnable noch nicht ausgeführt wurde... worauf verweist dann die Variable, wenn dessen run() Methode irgendwann mal ausgeführt wird?

Ich hatte gehofft, dass es so ähnlich läuft wie bei einer Variablenübergabe an eine Methode. Dort hin werden ja nur Kopien der Referenzen übergeben. So dass ein neues Zuweisen (innerhalb der Methode) keine Auswirkungen auf die original Referenz hat.

Irgendwo scheint da noch was zu passieren, was ich noch nicht ergründet habe. Man lernt also nie aus :)

Ehrlich gesagt versteh ich die Frage dabei nicht ganz?

Das läuft auch im wesentlichen wie Variablenübergabe an eine Methode, afaik wird da für die Anonyme Klasse ein Konstruktor aufgerufen und alle Variablen werden übergeben.

Ob das final dabei eine technische Voraussetzung ist, weiß ich ehrlich gesagt nicht, die JLS fordert es halt...
 

X5-599

Top Contributor
Dass für eine Anonyme Klasse ein Konstruktor aufgerufen wird und dahin die Variable(n) übergeben werden, hört sich gut an. Man sieht es halt nicht auf den ersten Blick, da man beim Beispiel Runnable ja nur eine Methode hat, die auf eine Variable außerhalb des Runnable-Objektes zuzugreifen scheint.

Doch verstehe ich nicht warum mein erstes Beispiel die Ausgabe auf der TextArea so durcheinander bringt (vom OOM Error mal ganz abgesehen). Jede gelesene Zeile des InputStreams müsste doch zu einen eigenen Runnable führen (dessen String Variable dann wohl ein Objekt Attribut ist), welches nacheinander auf dem EDT abgearbeitet wird.
 

mrBrown

Super-Moderator
Mitarbeiter
Dass für eine Anonyme Klasse ein Konstruktor aufgerufen wird und dahin die Variable(n) übergeben werden, hört sich gut an. Man sieht es halt nicht auf den ersten Blick, da man beim Beispiel Runnable ja nur eine Methode hat, die auf eine Variable außerhalb des Runnable-Objektes zuzugreifen scheint.
Das daraus ein Konstruktor-Aufruf wird, kann man durchaus auch als Implementierungsdetail sehen - für den Programmierer ist das irrelevant.

Das final dürfte im wesentlichen dazu dienen, dass der Programmierer damit klar kommt.
Aus der anonymen Klasse (bzw Lambda) kann man die "äußere" Variable sowieso nicht ändern - die Methode kann ja schon durchgelaufen und damit vom Stack sein.
Die Variable in der Methode ändern, führt dazu das die anonyme Klasse mit einem "alten" Stand arbeitet.
Mit final behebt man beide Probleme.

Doch verstehe ich nicht warum mein erstes Beispiel die Ausgabe auf der TextArea so durcheinander bringt (vom OOM Error mal ganz abgesehen). Jede gelesene Zeile des InputStreams müsste doch zu einen eigenen Runnable führen (dessen String Variable dann wohl ein Objekt Attribut ist), welches nacheinander auf dem EDT abgearbeitet wird.
Die neue Zeile wird immer an den String-Builder angehängt (er enthält also immer die gesamte Ausgabe).
Und das wird jedes mal an das Textfeld angehängt, in diesem steht also jeweils das, was vorher drin stand, und zusätzlich noch einmal die gesamte Ausgabe.
 

X5-599

Top Contributor
Also ich finde schon, dass dieses Detail für den Programmierer relevant ist. Ok, ich nutze solch ein Konstrukt relativ selten. Ich habe mich aber öfters gefragt, wieso eine Variable, die nur in einer (noch nicht ausgeführten) Methode einer anonymen Klasse referenziert wird nicht "verschwindet" (z.B. vom Garbage Collector abgeräumt) wenn die erzeugende Methode bereits durchgelaufen ist.

Jetzt sehe ich, dass die Variable, die einfach nur referenziert wird in der run() Methode, tatsächlich eine final Member Variable in der anonymen Runnable-Klasse geworden ist und auf einmal macht alles Sinn :)


Bei meinem Beispiel meinte ich, das wo ich den StringBuilder nicht benutze, sondern direkt an die TextArea appende. Das würde die Ausgabe durcheinander bringen wurde gesagt. Und das kann ich mir nicht erklären.
 

mrBrown

Super-Moderator
Mitarbeiter
Also ich finde schon, dass dieses Detail für den Programmierer relevant ist. Ok, ich nutze solch ein Konstrukt relativ selten. Ich habe mich aber öfters gefragt, wieso eine Variable, die nur in einer (noch nicht ausgeführten) Methode einer anonymen Klasse referenziert wird nicht "verschwindet" (z.B. vom Garbage Collector abgeräumt) wenn die erzeugende Methode bereits durchgelaufen ist.

Jetzt sehe ich, dass die Variable, die einfach nur referenziert wird in der run() Methode, tatsächlich eine final Member Variable in der anonymen Runnable-Klasse geworden ist und auf einmal macht alles Sinn :)
Relevant ist davon, dass sie in der anonymen Klasse noch referenziert wird - und was referenziert wird, wird nicht aufgeräumt.
Ob das intern über eine eigene Klasse und Konstruktor gelöst wird, ist dafür doch egal, spätestens mit Lambdas oder wenn der JIT-Compiler drüber gelaufen ist, ist eh wieder alles anders ;)


Bei meinem Beispiel meinte ich, das wo ich den StringBuilder nicht benutze, sondern direkt an die TextArea appende. Das würde die Ausgabe durcheinander bringen wurde gesagt. Und das kann ich mir nicht erklären.
Dein Beispiel wurde falsch umgesetzt ;)
 

ralfb1105

Bekanntes Mitglied
Hallo X5-599,

Vielen Dank für Deinen Lösungsvorschlag und die Implementierung. Ich habe das wie von Dir vorgeschlagen in mein Programm eingebaut und es kommt jetzt kein OOM Error mehr :) und es kommen auch keine doppelte Zeilen mehr - das war dann wohl mein Fehler, wie MrBrown richtig vermutet hat :(.

Auch wenn ich noch nicht alle Zeilen Deiner Implementierung verstanden habe, habe ich jetzt eine Lösung mit der ich weiter machen kann.

Hier noch einmal der komplette Code, als Referenz für andere Anfänger:
Java:
package gui;

import java.util.List;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.InputStreamReader;

import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

import javax.swing.WindowConstants;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;


/**
* This code was edited or generated using CloudGarden's Jigloo
* SWT/Swing GUI Builder, which is free for non-commercial
* use. If Jigloo is being used commercially (ie, by a corporation,
* company or business for any purpose whatever) then you
* should purchase a license for each developer using Jigloo.
* Please visit www.cloudgarden.com for details.
* Use of Jigloo implies acceptance of these licensing terms.
* A COMMERCIAL LICENSE HAS NOT BEEN PURCHASED FOR
* THIS MACHINE, SO JIGLOO OR THIS CODE CANNOT BE USED
* LEGALLY FOR ANY CORPORATE OR COMMERCIAL PURPOSE.
*/


@SuppressWarnings("serial")
public class ProcessBuilderGUI extends javax.swing.JFrame {
    private JTextArea jTConsoleOutput;
    private JScrollPane jScrollPane1;
    private JButton JBClearConsole;
    private JButton JBStart;
    private JButton JBExit;
    private JLabel jLHeaderText;

    /**
    * Auto-generated main method to display this JFrame
    */
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                ProcessBuilderGUI inst = new ProcessBuilderGUI();
                inst.setLocationRelativeTo(null);
                inst.setVisible(true);
            }
        });
    }
   
    public ProcessBuilderGUI() {
        super();
        initGUI();
    }
   
    class ProcessWorker extends SwingWorker<Void, String>
    {
        private JTextArea jTConsoleOutput;
       
        public ProcessWorker(JTextArea textArea)
        {
            this.jTConsoleOutput = textArea;
        }
       
       
        @Override
        protected Void doInBackground() throws Exception
        {
            //hier läuft alles in einem eigenen Thread (nebenläufig) ab. Es ist also egal wie lange die Abarbeitung hier dauert.
            BufferedReader reader = null;
           
            try
            {
                ProcessBuilder pb = new ProcessBuilder("/Users/ralfb/Progs/test4.sh");
                Process p = pb.start();
                reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
               
                String line = null;
                while((line = reader.readLine()) != null)
                {
                    // dieser Aufruf führt (über Umwege) dazu, dass die untere process(List<String>) Methode aufgerufen wird.
                    publish(line);
                }
            }
            finally
            {
                if(reader != null)
                {
                    reader.close();
                }
            }
           
            return null;
        }
       
        @Override
        protected void process(List<String> chunks)
        {
            //hier läuft alles auf dem EDT (Event Dispatch Thread). Darum können (und sollten) wir hier die GUI Komponenten manipulieren.
            for(int i = 0; i < chunks.size(); i++)
            {
                jTConsoleOutput.append(chunks.get(i));
                jTConsoleOutput.append(System.getProperty("line.separator"));
            }
        }
    }
   
    private void initGUI() {
        try {
            setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
            getContentPane().setLayout(null);
            {
                jScrollPane1 = new JScrollPane();
                getContentPane().add(jScrollPane1);
                jScrollPane1.setBounds(38, 48, 606, 396);
                {
                    jTConsoleOutput = new JTextArea();
                    jScrollPane1.setViewportView(jTConsoleOutput);
                    jTConsoleOutput.setBounds(38, 48, 606, 396);
                    jTConsoleOutput.setEditable(false);
                    jTConsoleOutput.setBackground(new java.awt.Color(255,255,236));
                }
            }
            {
                jLHeaderText = new JLabel();
                getContentPane().add(jLHeaderText);
                jLHeaderText.setText("  Command Output Console");
                jLHeaderText.setBounds(38, 19, 605, 29);
                jLHeaderText.setFont(new java.awt.Font("Segoe UI",1,16));
                jLHeaderText.setForeground(new java.awt.Color(255,0,0));
            }
            {
                JBExit = new JButton();
                getContentPane().add(JBExit);
                JBExit.setText("Exit");
                JBExit.setBounds(661, 410, 87, 34);
                JBExit.setFont(new java.awt.Font("Segoe UI",0,16));
                JBExit.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent evt) {
                        JBExitActionPerformed(evt);
                    }
                });
            }
            {
                JBStart = new JButton();
                getContentPane().add(JBStart);
                JBStart.setText("Start");
                JBStart.setBounds(661, 333, 87, 38);
                JBStart.setFont(new java.awt.Font("Segoe UI",0,16));
                JBStart.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent evt) {
                        JBStartActionPerformed(evt);
                    }
                });
            }
            {
                JBClearConsole = new JButton();
                getContentPane().add(JBClearConsole);
                JBClearConsole.setText("Clear Console");
                JBClearConsole.setBounds(38, 463, 138, 36);
                JBClearConsole.setFont(new java.awt.Font("Segoe UI",0,16));
                JBClearConsole.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent evt) {
                        JBClearConsoleActionPerformed(evt);
                    }
                });
            }
            pack();
            this.setSize(823, 561);
        } catch (Exception e) {
            //add your error handling code here
            e.printStackTrace();
        }
    }
   
    private void JBExitActionPerformed(ActionEvent evt) {
        System.exit(0);
    }
   
    private void JBClearConsoleActionPerformed(ActionEvent evt) {
        jTConsoleOutput.setText("");
    }
   
    private void JBStartActionPerformed(ActionEvent evt) {
        ProcessWorker p = new ProcessWorker(jTConsoleOutput);
        p.execute();
    }
}

Wie gesagt, VIELEN DANK an alle, Ihr habt mich ein großes Stück weiter gebracht, auch wenn ich noch am Anfang stehe was das Thema Java Programmierung angeht. Das Forum ist wirklich sehr hilfreich und Euer Engagement ist echt toll!!

Gruß

Ralf
 
Ähnliche Java Themen
  Titel Forum Antworten Datum
M Wie kommen diese Ausgaben zustande? Java Basics - Anfänger-Themen 12
K Ausgaben auf der Konsole (static Varible) Java Basics - Anfänger-Themen 9
B Grundsatzfragen zu meinem neuen Projekt Einnahmen-Ausgaben App Java Basics - Anfänger-Themen 8
A Wieso bekomme ich hier zwei unterschiedliche Ausgaben? Java Basics - Anfänger-Themen 6
S Ein- Ausgaben Java [Hilfe] Java Basics - Anfänger-Themen 3
M Verschiedene Eingabe = Verschiedene Ausgaben Java Basics - Anfänger-Themen 5
J Erste Schritte Alle möglichen ausgaben von 5 Zahlen als Vector Java Basics - Anfänger-Themen 7
D Methoden Eigene Methode um alle Ausgaben aufzurufen Java Basics - Anfänger-Themen 17
C Vererbung - Ausgaben bestimmen Java Basics - Anfänger-Themen 6
O Wie Log4J - Ausgaben/Events auffangen?! Java Basics - Anfänger-Themen 3
J ausgaben von der konsole in das fenster Java Basics - Anfänger-Themen 5
W Array in String und String in Array - falsche Ausgaben Java Basics - Anfänger-Themen 20
S write(), weshalb verschiedene Ausgaben? Java Basics - Anfänger-Themen 4
V Personenverwaltung mit List<>, falsche Ausgaben Java Basics - Anfänger-Themen 5
Z GUI-Ausgaben. Java Basics - Anfänger-Themen 11
G Wie bekomme ich alle Ausgaben von runTime.exec() Java Basics - Anfänger-Themen 7
C Erste Schritte Boolsche For-Schleife soll Ausgaben mitzählen Java Basics - Anfänger-Themen 8
W Ausgaben in JAVA Java Basics - Anfänger-Themen 7
J Eclipse Console Ausgaben Abfangen Java Basics - Anfänger-Themen 2
H Mysteriöse Ausgaben nach Addition Java Basics - Anfänger-Themen 5
D Methoden und Ausgaben Java Basics - Anfänger-Themen 4
A [gelöst]Zwei Ausgaben, die eigentlich gleich sein sollten Java Basics - Anfänger-Themen 9
S Hilfe bei Ausgaben durch Konsolenprogramm Java Basics - Anfänger-Themen 13
Screen Verzögerung zwischen Ausgaben Java Basics - Anfänger-Themen 5
O 2 Ausgaben, aber nur 1 gewollt. Java Basics - Anfänger-Themen 10
J Verschiedene Ausgaben bei gleichen Ausdrücken (Typecasting?) Java Basics - Anfänger-Themen 5
G Farbige / unterstrichene Ausgaben auf der Konsole! Java Basics - Anfänger-Themen 2
G Erstellung von HTML Ausgaben Java Basics - Anfänger-Themen 5
F DOS Ausgaben einlesen Java Basics - Anfänger-Themen 14
S exe-Datei ausführen, Ausgaben einlesen? Java Basics - Anfänger-Themen 3
K String in JTextfield formatiert ausgaben Java Basics - Anfänger-Themen 5
S Bei Ausgaben von Objekten wird Klassenname@Zahl ausgegeben Java Basics - Anfänger-Themen 9
R Ausgaben von externer .exe einlesen Java Basics - Anfänger-Themen 2
C Ausgaben mit der Paint-Methode Java Basics - Anfänger-Themen 5
J Anfänger: ActionListener und ProcessBuilder machen Probleme Java Basics - Anfänger-Themen 6
W ProcessBuilder InputStream in Array speichern Java Basics - Anfänger-Themen 3
S Befehle in ProcessBuilder nachschieben Java Basics - Anfänger-Themen 2
D Cmd xcopy processbuilder Java Basics - Anfänger-Themen 6
Q ProcessBuilder kann datei nicht finden Java Basics - Anfänger-Themen 2
W Java ProcessBuilder - externer Prozess nur einmal starten und mehrere Inputs geben Java Basics - Anfänger-Themen 7
A ProcessBuilder problem Java Basics - Anfänger-Themen 4
G ProcessBuilder Java Basics - Anfänger-Themen 9
W ProcessBuilder Problem -->gelöst - - Jetzt JEditorPane Problem Java Basics - Anfänger-Themen 6
I gibt es ein Verb beim ProcessBuilder? Java Basics - Anfänger-Themen 6
N ProcessBuilder Java Basics - Anfänger-Themen 4
G Problem mit ProcessBuilder und "rm" unter Linux Java Basics - Anfänger-Themen 4
C Konstruktor ProcessBuilder Java Basics - Anfänger-Themen 11
M ProcessBuilder und OutputStream Java Basics - Anfänger-Themen 2
B Thread / Prozess stoppen? Java Basics - Anfänger-Themen 22
A Input/Output Prozess Output genauso in der Konsole ausgeben Java Basics - Anfänger-Themen 0
P Threads Prozess kann nicht über die GUI gestartet werden Java Basics - Anfänger-Themen 8
B Gleicher Prozess starten und stoppen (Problem beim Stoppen) Java Basics - Anfänger-Themen 5
B MySQL starten / stoppen -> Stoppen erzeugt neuen Prozess Java Basics - Anfänger-Themen 0
GENiALi Java Console Anwendung starten in Prozess Java Basics - Anfänger-Themen 3
I Datei wird von anderen Prozess verwendet Java Basics - Anfänger-Themen 10
J Threads Prozess in Thread auslagern Java Basics - Anfänger-Themen 2
B eingene Klasse in eigenem Prozess starten mit relativem Pfad Java Basics - Anfänger-Themen 5
X Vollkommen Unabhängigen Prozess starten Java Basics - Anfänger-Themen 8
L Prozess beenden Java Basics - Anfänger-Themen 3
C Prozess wird erst bei Beendigung des Programms ausgeführt Java Basics - Anfänger-Themen 2
C Prozess Fehlerbehandlung Java Basics - Anfänger-Themen 2
M prozess starten und warten bis dieser sich beendet Java Basics - Anfänger-Themen 3
G Externen Prozess starten - entweder stdin oder stderr auslesen Java Basics - Anfänger-Themen 3
B Prozess wird nicht beendet Java Basics - Anfänger-Themen 2
S Prozess auslesen und starten Java Basics - Anfänger-Themen 9
M prozess-managment Windows java Java Basics - Anfänger-Themen 4
L Prozess rückgabewert? Java Basics - Anfänger-Themen 5
T BufferedReader oder Prozess, wer hängt sich auf? Java Basics - Anfänger-Themen 6
S mit getRuntime.exec gestarteten Prozess überwachen Java Basics - Anfänger-Themen 2
P Kommunikation mit Prozess Java Basics - Anfänger-Themen 3
S Prozess paralell starten Java Basics - Anfänger-Themen 5
T Öffnen externer Datei als eigener Prozess Java Basics - Anfänger-Themen 8
M prüfen, ob Prozess fertig mit Abarbeitung. wie? Java Basics - Anfänger-Themen 11
N cmd aus java starten; prozess beenden Java Basics - Anfänger-Themen 11
R Warten, bis ein Prozess gestartet wurde Java Basics - Anfänger-Themen 23
ven000m Java Prozess beenden? Java Basics - Anfänger-Themen 2

Ähnliche Java Themen

Neue Themen


Oben