Laden...

RedirectStandardOutput DosShell unter W7 und neuer

Erstellt von Zerstreute vor 4 Jahren Letzter Beitrag vor 4 Jahren 1.443 Views
Z
Zerstreute Themenstarter:in
7 Beiträge seit 2017
vor 4 Jahren
RedirectStandardOutput DosShell unter W7 und neuer

Hallo Zusammen,

ich habe mich mal wieder festgefressen bei einem Problem das wahrscheinlich viel einfacher zu lösen ist als ich denke.

Folgendes habe ich vor :

Ich schreibe an einer GUI für einen Ton-Generator der als Commandozeilentool vorliegt.
Dieser Generator wird mit Angabe von SampleFrequenz und Sinusfrequenz und weiteren Parametern gestartet, läuft dann im Fenster so lange bis man eine Taste drückt. In dieser Zeit läuft in diesem Fenster noch ein Zähler wodurch der Datenstrom in dem Fenster nicht aufhört bis man das Programm beendet oder im Fenster die Space Taste drückt.

Eine Teilfunktion meines Tools soll sein die möglichen Samplefrequenzen der ausgewählten Soundkarte zu erkennen.

Dazu starte ich das Tool einmal mit jeder bekannten Samplefrequenz und möchte eigentlich gern das er soweit kein Fehler auftritt sofort das Programm beendet und mit der nächsten Frequenz weiter macht. Leider wird die von mir vorgesehene Umleitung des Output Stroms nicht wie gewünscht angesprungen wodurch das Programm in eine Endlosschleife gerät und nicht weiter arbeitet bis ich das externe Fenster schließe.

Hier ist mein bisheriger Lösungansatz, der leider nicht wie gewünscht funktioniert :


...
Process Gen = new Process();
...
                        void Button_testClick(object sender, EventArgs e)
		{
			
			Gen.StartInfo.UseShellExecute=false;
			Gen.StartInfo.RedirectStandardOutput=true;
			Gen.OutputDataReceived +=proc_OutputDataReceived;
			Gen.StartInfo.RedirectStandardError=true;
			Gen.StartInfo.CreateNoWindow=false;
			
			for (int i=0;i<comboBox_SampleFreq.Items.Count;i++)
			{
				
				comboBox_SampleFreq.SelectedIndex=i;
				KillTasks();
				Gen.StartInfo.Arguments=Param.AsioArguments;

				Gen.Start();
				Gen.BeginOutputReadLine();
				if (Gen.StandardError.Peek()>0)
				{
					comboBox_SampleFreq.Items.RemoveAt(i--);
					
				}

			}
			Gen.OutputDataReceived -=proc_OutputDataReceived;
			comboBox_SampleFreq.SelectedIndex=0;
		}
		void proc_OutputDataReceived(object sender, DataReceivedEventArgs e)
		{
				string text =  e.Data;		
				KillTasks();
		}
		void KillTasks()
		{
			foreach (Process p in Process.GetProcessesByName(AsioTool.Replace(".exe",""))) {
				p.Kill();
				Thread.Sleep(500);
			}
		}

Hängt das Tool weil ich den Datenstrom im selben Thread laufen lasse wie die Hauptanwendung ? Oder wo liegt hier das Problem das nach ausführung von Gen.Start() alles wartet ?

Würde mich hier über Kritik, Anregungen und Hilfe sehr freuen.

Danke schonmal im Vorraus

T
156 Beiträge seit 2010
vor 4 Jahren

Hallo,

also sowas ist böse:

comboBox_SampleFreq.SelectedIndex=i;

Was der User ausgewählt hat, solltest Du so belassen. Das gibt sonst nur Probleme.
Und wozu das?

Viel schlimmer ist aber noch das:


if (Gen.StandardError.Peek()>0)
{
   comboBox_SampleFreq.Items.RemoveAt(i--);
}

Was kann die "arme ComboBox" dafür, dass Fehler auftreten? Die Items sollten hier ziemlich konstant sein!

T
156 Beiträge seit 2010
vor 4 Jahren

Ich sehe da eindeutig zu viele

KillTasks();
T
2.219 Beiträge seit 2008
vor 4 Jahren

Un das Thread.Sleep(500); sieht auch noch sehr falsch aus.
Dadurch blockierst du deine UI bei jedem Aufruf aktuell 500ms lang.
Das dürfte den Nutzer auch nicht freuen, wenn das Programm immer wieder ohne Erklärung für ihn hängt.

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

Z
Zerstreute Themenstarter:in
7 Beiträge seit 2017
vor 4 Jahren

Ich sehe da eindeutig zu viele

KillTasks();  

tja .. leider ist die einzige Alternative die kontrolle über das DOS Tool zu nehmen und über windows an das Fenster den Druck der Space Taste zu simulieren - was am Ende übrigens nix anderes macht als das Programm zu beenden..

Vom Ablauf her sieht das so aus :

Programm mit Parametern starten .. dieses läuft dann im Shell Fenster und wartet auf "Space"-Druck in dieser Zeit zählt einfach nur ein Zähler die bereits abgespielten Samples.

Über 'Kill Prozess' stelle ich nur sicher das definitiv immer nur eine Instanz des Programms läuft da 2 Instanzen nicht gleichzeitig auf die ASIO Schnittstelle zugreifen können.

Was das Anpassen der KomboBox betrifft, hier wird diese Schleife vor der Freigabe der Box für den Benutzer modifiziert. Im Idealfall würde die Prüfung und Anpassung innerhalb weniger Millisekunden abgeschlossen sein. Der Grund für dieses unschöne Vorgehen ist, das ich über das externe Tool die "angeblich möglichen Samplefrequenzen" abfragen kann aber es mir leider immer alle von sich selbst unterstützten, jedoch nicht nur die von der Hardware unterstützen Frequenzen angibt. Daher muß ich, um es für den Benutzer möglichst ohne Fehlermeldungen laufen zu lassen, die Combobox immer beim Wechsel des ASIO Devices neu befüllen und dabei die nicht unterstützten entfernen.

Was ich mir jedoch als alternative Lösung vorstellen könnte, wäre:
Ich prüfe nur die kritischen Frequenzen und befülle dann die ComboBox mit den übrigen Frequenzen. Das wäre sicherlich auch vom Code her deutlich schöner und käme auch mit weniger Kills aus.

Abschließend nur ein Hinweis auf die Sleeps. Diese habe ich hinzugefügt weil es ohne Wartezeit z.T. Probleme beim Start des Shell Programms gab. Hier könnte ich sicherlich auch die Wartezeit noch etwas reduzieren.

Am Ende habe ich jetzt aber irgendwie den Eindruck das keiner von Euch mir sagen kann warum mein Programm hängt sobald das externe Tool gestartet wurde (und ohne Fehler läuft).

T
2.219 Beiträge seit 2008
vor 4 Jahren

Um nur eine Instanz deines Programms zu erlauben, solltest du ein Mutex verwenden.
Damit setzt du Systemweit ein Lock in einem Programm, musst du dann nur beim beenden wieder freigeben.
Alternativ zum Mutex kannst du dir auch über die Process Klasse alle laufenden Prozesse holen und schauen ob deiner gerade ausgeführt wird mit einer anderen Process ID als deiner aktuellen.
Damit sparst du dir das KillTasks eigentlich komplett weg.

Dein Programm wird entweder in deinem KillTasks oder an einer anderen Stelle hängen.
Dies kannst du per Debugger prüfen, ansonsten solltest du dir auch ein Log schreiben um deinem Problem auf die Schliche zu kommen.

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

3.170 Beiträge seit 2006
vor 4 Jahren

Hallo,

das keiner von Euch mir sagen kann warum mein Programm hängt sobald das externe Tool gestartet wurde

Eine Vermutung hätte ich schon anzubieten: das OutputDataReceived-Event wird nur ausgelöst, wenn das externe Programm eine vollständige Zeile inklusive Zeilenumbruch schreibt.
Vermutlich ist das nicht der Fall, dann müsstest Du selbst direkt auf dem StandardOutput-Stream lesen.

Gruß, MarsStein

Non quia difficilia sunt, non audemus, sed quia non audemus, difficilia sunt! - Seneca

Z
Zerstreute Themenstarter:in
7 Beiträge seit 2017
vor 4 Jahren

Eine Vermutung hätte ich schon anzubieten: das OutputDataReceived-Event wird nur ausgelöst, wenn das externe Programm eine vollständige Zeile inklusive Zeilenumbruch schreibt.

Hallo MarsStein,

so was in der Art ist auch meine Vermutung da sobald ich das externe Programm starte auch mein Programm im Debugger keine Zeile weiter abarbeitet. Sprich sobald ich im Einzelschritt Betrieb den Process.Start() ausgeführt hab, gehts einfach im Programm nirgends weiter und eine Fehlermeldung bekomme ich auch nicht.

Obwohl im Fenster (bei nicht umgeleitetem Output) mehrere Zeilen stehen, scheint der StreamReader nicht ausgelöst zu werden. Ich befürchte beinahe das im externen Programm noch ein Programm oder Prozess gestartet wird der nicht im Output Stream ausgegeben wird. (ähnlich wie bei Putty)

könnte ich sowas irgendwie heraus bekommen ?

Gruß und Dank schonmal an alle