24 Stimmen

Live-Ausgabe von Prozess abrufen

Ich habe ein Problem in meinem Projekt. Ich möchte einen Prozess starten, 7z.exe (Konsolenversion). Ich habe drei verschiedene Dinge versucht:

  • Process.StandardOutput.ReadToEnd();
  • OutputDataReceived & BeginOutputReadLine
  • StreamWriter

Nichts funktioniert. Es ist immer "warten" für das Ende des Prozesses zu zeigen, was ich will. Ich habe keinen Code zu setzen, nur wenn Sie meinen Code mit einem der Dinge, die dort oben aufgeführt sind, wollen. Danke!

Bearbeiten: Mein Code:

        process.StartInfo.UseShellExecute = false;
        process.StartInfo.RedirectStandardOutput = true;
        process.StartInfo.CreateNoWindow = true;
        process.Start();

        this.sr = process.StandardOutput;
        while (!sr.EndOfStream)
        {
            String s = sr.ReadLine();
            if (s != "")
            {
                System.Console.WriteLine(DateTime.Now + " - " + s);
            }
        }

Oder

process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.OutputDataReceived += new DataReceivedEventHandler(recieve);
process.StartInfo.CreateNoWindow = true;
process.Start();
process.BeginOutputReadLine();
process.WaitForExit();
public void recieve(object e, DataReceivedEventArgs outLine)
{
    System.Console.WriteLine(DateTime.Now + " - " + outLine.Data);
}

Oder

process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.Start();
string output = p.StandardOutput.ReadToEnd();
process.WaitForExit();

Wobei "Prozess" mein vorgefertigter Prozess ist.

Okay, ich weiß, warum es nicht richtig funktioniert: 7z.exe ist der Fehler: es zeigt eine prozentuale Ladezeit in der Konsole an, und es sendet nur Informationen, wenn die aktuelle Datei fertig ist. Beim Extrahieren zum Beispiel funktioniert es gut :). Ich werde nach einer anderen Möglichkeit suchen, die 7z-Funktionen ohne 7z.exe zu nutzen (vielleicht mit 7za.exe oder mit einer DLL). Vielen Dank an alle. Um auf die Frage zu antworten, das Ereignis OuputDataRecieved funktioniert einwandfrei!

30voto

Michiel van Vaardegem Punkte 2230

Werfen Sie einen Blick auf diese Seite, es sieht so aus, als wäre das die Lösung für Sie: http://msdn.microsoft.com/en-us/library/system.diagnostics.process.beginoutputreadline.aspx y http://msdn.microsoft.com/en-us/library/system.diagnostics.process.standardoutput.aspx

[Bearbeiten] Dies ist ein funktionierendes Beispiel:

        Process p = new Process();
        p.StartInfo.RedirectStandardError = true;
        p.StartInfo.RedirectStandardOutput = true;
        p.StartInfo.UseShellExecute = false;
        p.StartInfo.CreateNoWindow = true;
        p.StartInfo.FileName = @"C:\Program Files (x86)\gnuwin32\bin\ls.exe";
        p.StartInfo.Arguments = "-R C:\\";

        p.OutputDataReceived += new DataReceivedEventHandler((s, e) => 
        { 
            Console.WriteLine(e.Data); 
        });
        p.ErrorDataReceived += new DataReceivedEventHandler((s, e) =>
        {
            Console.WriteLine(e.Data);
        });

        p.Start();
        p.BeginOutputReadLine();
        p.BeginErrorReadLine();

Übrigens, ls -R C:\ listet alle Dateien von der Wurzel von C: rekursiv auf. Das sind eine Menge Dateien, und ich bin sicher, dass dies nicht getan ist, wenn die ersten Ergebnisse auf dem Bildschirm erscheinen. Es besteht die Möglichkeit, dass 7zip die Ausgabe zurückhält, bevor sie angezeigt wird. Ich bin nicht sicher, welche Parameter Sie dem Prozess übergeben.

9voto

user3042599 Punkte 477

Um die Ausgabe- und/oder Fehlerumleitung korrekt zu handhaben, müssen Sie auch die Eingabe umleiten. Es scheint ein Feature/Fehler in der Laufzeit der externen Anwendung zu sein, die Sie starten, und von dem, was ich bisher gesehen habe, wird es nirgendwo sonst erwähnt.

Beispiel für die Verwendung:

        Process p = new Process(...);

        p.StartInfo.UseShellExecute = false;
        p.StartInfo.RedirectStandardOutput = true;
        p.StartInfo.RedirectStandardError = true;
        p.StartInfo.RedirectStandardInput = true; // Is a MUST!
        p.EnableRaisingEvents = true;

        p.OutputDataReceived += OutputDataReceived;
        p.ErrorDataReceived += ErrorDataReceived;

        Process.Start();

        p.BeginOutputReadLine();
        p.BeginErrorReadLine();

        p.WaitForExit();

        p.OutputDataReceived -= OutputDataReceived;
        p.ErrorDataReceived -= ErrorDataReceived;

...

    void OutputDataReceived(object sender, DataReceivedEventArgs e)
    {
        // Process line provided in e.Data
    }

    void ErrorDataReceived(object sender, DataReceivedEventArgs e)
    {
        // Process line provided in e.Data
    }

7voto

user2848240 Punkte 91

Ich weiß nicht, ob noch jemand nach einer Lösung für dieses Problem sucht, aber es ist mir schon mehrmals untergekommen, weil ich ein Tool in Unity zur Unterstützung einiger Spiele schreibe und aufgrund der eingeschränkten Interoperabilität bestimmter Systeme mit mono (wie z.B. PIA zum Lesen von Text aus Word), muss ich oft OS-spezifische (manchmal Windows, manchmal MacOS) ausführbare Dateien schreiben und sie über Process.Start() starten.

Das Problem ist, wenn Sie eine ausführbare Datei wie diese starten, wird sie in einem anderen Thread gestartet, der Ihre Hauptanwendung blockiert und einen Hänger verursacht. Wenn Sie Ihren Benutzern während dieser Zeit nützliches Feedback geben wollen, das über die sich drehenden Symbole hinausgeht, die Ihr jeweiliges Betriebssystem hervorzaubert, dann sind Sie ziemlich aufgeschmissen. Die Verwendung eines Streams wird nicht funktionieren, da der Thread immer noch blockiert ist, bis die Ausführung beendet ist.

Die Lösung, auf die ich gestoßen bin und die für einige Leute extrem erscheinen mag, aber für mich ganz gut funktioniert, besteht darin, Sockets und Multithreading zu verwenden, um zuverlässige synchrone Kommunikation zwischen den beiden Anwendungen einzurichten. Das funktioniert natürlich nur, wenn Sie beide Anwendungen erstellen. Wenn nicht, haben Sie wohl Pech gehabt. ... Ich möchte sehen, ob es mit nur Multithreading mit einem traditionellen Stream-Ansatz funktioniert, so dass, wenn jemand möchte, dass zu versuchen und die Ergebnisse hier posten, das wäre toll.

Wie auch immer, hier ist die Lösung, die derzeit bei mir funktioniert:

In der Hauptanwendung oder der aufrufenden Anwendung gehe ich folgendermaßen vor:

/// <summary>
/// Handles the OK button click.
/// </summary>
private void HandleOKButtonClick() {
string executableFolder = "";

#if UNITY_EDITOR
executableFolder = Path.Combine(Application.dataPath, "../../../../build/Include/Executables");
#else
executableFolder = Path.Combine(Application.dataPath, "Include/Executables");
#endif

EstablishSocketServer();

var proc = new Process {
    StartInfo = new ProcessStartInfo {
        FileName = Path.Combine(executableFolder, "WordConverter.exe"),
        Arguments = locationField.value + " " + _ipAddress.ToString() + " " + SOCKET_PORT.ToString(), 
        UseShellExecute = false,
        RedirectStandardOutput = true,
        CreateNoWindow = true
    }
};

proc.Start();

Hier richte ich den Socket-Server ein:

/// <summary>
/// Establishes a socket server for communication with each chapter build script so we can get progress updates.
/// </summary>
private void EstablishSocketServer() {
    //_dialog.SetMessage("Establishing socket connection for updates. \n");
    TearDownSocketServer();

    Thread currentThread;

    _ipAddress = Dns.GetHostEntry(Dns.GetHostName()).AddressList[0];
    _listener = new TcpListener(_ipAddress, SOCKET_PORT);
    _listener.Start();

    UnityEngine.Debug.Log("Server mounted, listening to port " + SOCKET_PORT);

    _builderCommThreads = new List<Thread>();

    for (int i = 0; i < 1; i++) {
        currentThread = new Thread(new ThreadStart(HandleIncomingSocketMessage));
        _builderCommThreads.Add(currentThread);
        currentThread.Start();
    }
}

/// <summary>
/// Tears down socket server.
/// </summary>
private void TearDownSocketServer() {
    _builderCommThreads = null;

    _ipAddress = null;
    _listener = null;
}

Hier ist meine Socket-Handler für den Thread ... beachten Sie, dass Sie mehrere Threads in einigen Fällen erstellen müssen; Das ist, warum ich, dass _builderCommThreads Liste in dort haben (ich portiert es von Code anderswo, wo ich etwas ähnliches tat, aber mehrere Instanzen in einer Reihe aufrufen):

/// <summary>
/// Handles the incoming socket message.
/// </summary>
private void HandleIncomingSocketMessage() {
    if (_listener == null) return;

    while (true) {
        Socket soc = _listener.AcceptSocket();
        //soc.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 10000);
        NetworkStream s = null;
        StreamReader sr = null;
        StreamWriter sw = null;
        bool reading = true;

        if (soc == null) break;

        UnityEngine.Debug.Log("Connected: " + soc.RemoteEndPoint);

        try {
            s = new NetworkStream(soc);
            sr = new StreamReader(s, Encoding.Unicode);
            sw = new StreamWriter(s, Encoding.Unicode);
            sw.AutoFlush = true; // enable automatic flushing

            while (reading == true) {
                string line = sr.ReadLine();

                if (line != null) {
                    //UnityEngine.Debug.Log("SOCKET MESSAGE: " + line);
                    UnityEngine.Debug.Log(line);

                    lock (_threadLock) {
                        // Do stuff with your messages here
                    }
                }
            }

            //
        } catch (Exception e) {
            if (s != null) s.Close();
            if (soc != null) soc.Close();
            UnityEngine.Debug.Log(e.Message);
            //return;
        } finally {

        //
        if (s != null) s.Close();
        if (soc != null) soc.Close();

        UnityEngine.Debug.Log("Disconnected: " + soc.RemoteEndPoint);
        }
    }

    return;
}

Natürlich müssen Sie einige Dinge oben angeben:

private TcpListener _listener = null;
private IPAddress _ipAddress = null;
private List<Thread> _builderCommThreads = null;
private System.Object _threadLock = new System.Object();

...dann in der aufgerufenen ausführbaren Datei das andere Ende einrichten (ich habe in diesem Fall Statics verwendet, Sie können verwenden, was immer Sie wollen):

private static TcpClient _client = null;
private static Stream _s = null;
private static StreamReader _sr = null;
private static StreamWriter _sw = null;
private static string _ipAddress = "";
private static int _port = 0;
private static System.Object _threadLock = new System.Object();

/// <summary>
/// Main method.
/// </summary>
/// <param name="args"></param>
static void Main(string[] args) {
    try {
        if (args.Length == 3) {
            _ipAddress = args[1];
            _port = Convert.ToInt32(args[2]);

            EstablishSocketClient();
        }

        // Do stuff here

        if (args.Length == 3) Cleanup();
    } catch (Exception exception) {
        // Handle stuff here
        if (args.Length == 3) Cleanup();
    }
}

/// <summary>
/// Establishes the socket client.
/// </summary>
private static void EstablishSocketClient() {
    _client = new TcpClient(_ipAddress, _port);

    try {
        _s = _client.GetStream();
        _sr = new StreamReader(_s, Encoding.Unicode);
        _sw = new StreamWriter(_s, Encoding.Unicode);
        _sw.AutoFlush = true;
    } catch (Exception e) {
        Cleanup();
    }
}

/// <summary>
/// Clean up this instance.
/// </summary>
private static void Cleanup() {
    _s.Close();
    _client.Close();

    _client = null;
    _s = null;
    _sr = null;
    _sw = null;
}

/// <summary>
/// Logs a message for output.
/// </summary>
/// <param name="message"></param>
private static void Log(string message) {
    if (_sw != null) {
        _sw.WriteLine(message);
    } else {
        Console.Out.WriteLine(message);
    }
}

...Ich benutze dies, um ein Kommandozeilen-Tool auf Windows zu starten, das das PIA-Zeug verwendet, um Text aus einem Word-Dokument zu ziehen. Ich habe versucht, PIA die .dlls in Unity, aber lief in Interop Probleme mit Mono. Ich verwende es auch auf MacOS, um Shell-Skripte aufzurufen, die zusätzliche Unity-Instanzen im Batch-Modus starten und in diesen Instanzen Editor-Skripte ausführen, die über diese Socket-Verbindung mit dem Tool kommunizieren. Das ist großartig, denn ich kann jetzt Feedback an den Benutzer senden, debuggen, überwachen und auf bestimmte Schritte im Prozess reagieren, und so weiter und so fort.

HTH

6voto

Bork Blatt Punkte 3270

Das Problem wird verursacht durch den Aufruf der Process.WaitForExit 方法 . Die Dokumentation besagt, dass dies folgendermaßen funktioniert:

Legt die Zeitspanne fest, die gewartet werden soll, bis der zugehörige Prozess beendet wird, und blockiert den aktuellen Thread der Ausführung, bis die Zeit verstrichen ist oder der Prozess beendet ist. Um das Blockieren des aktuellen Threads zu vermeiden, verwenden Sie das Ereignis Exited .

Um also zu verhindern, dass der Thread blockiert, bis der Prozess beendet ist, verdrahten Sie die Process.Exited Veranstaltung Handler des Process-Objekts, wie unten dargestellt. Das Ereignis Exited kann nur auftreten, wenn der Wert der Eigenschaft EnableRaisingEvents true ist.

    process.EnableRaisingEvents = true;
    process.Exited += Proc_Exited;

    private void Proc_Exited(object sender, EventArgs e)
    {
        // Code to handle process exit
    }

Auf diese Weise können Sie die Ausgabe Ihres Prozesses abrufen, während er noch läuft, und zwar über die Prozess.OutputDataReceived Veranstaltung wie Sie es derzeit tun. (PS - das Codebeispiel auf dieser Ereignisseite macht auch den Fehler, Process.WaitForExit zu verwenden).

Ein weiterer Hinweis ist, dass Sie sicherstellen müssen, dass Ihr Prozessobjekt nicht aufgeräumt wird, bevor Ihre Exited-Methode ausgelöst werden kann. Wenn Ihr Process in einer using-Anweisung initialisiert wird, könnte dies ein Problem darstellen.

2voto

joebalt Punkte 909

Ich habe die beschriebene CmdProcessor-Klasse verwendet aquí an mehreren Projekten mit großem Erfolg beteiligt. Auf den ersten Blick sieht es etwas abschreckend aus, aber es ist sehr einfach zu bedienen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X