it-swarm.com.de

ProcessStartInfo hängt an "WaitForExit"? Warum?

Ich habe folgenden Code:

info = new System.Diagnostics.ProcessStartInfo("TheProgram.exe", String.Join(" ", args));
info.CreateNoWindow = true;
info.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
info.RedirectStandardOutput = true;
info.UseShellExecute = false;
System.Diagnostics.Process p = System.Diagnostics.Process.Start(info);
p.WaitForExit();
Console.WriteLine(p.StandardOutput.ReadToEnd()); //need the StandardOutput contents

Ich weiß, dass die Ausgabe des Prozesses, den ich starte, etwa 7 MB lang ist. Das Ausführen in der Windows-Konsole funktioniert einwandfrei. Leider hängt dies programmgesteuert leider unbegrenzt bei WaitForExit. Beachten Sie auch, dass der Code NICHT für kleinere Ausgänge (wie 3 KB) hängt.

Ist es möglich, dass der interne StandardOutput in ProcessStartInfo 7 MB nicht zwischenspeichern kann? Wenn ja, was soll ich stattdessen tun? Wenn nicht, was mache ich falsch?

163
Epaga

Das Problem ist, dass, wenn Sie StandardOutput und/oder StandardError umleiten, der interne Puffer voll werden kann. Welche Reihenfolge auch immer Sie verwenden, es kann ein Problem geben:

  • Wenn Sie warten, bis der Prozess beendet ist, bevor Sie StandardOutput lesen, kann der Prozess den Versuch des Schreibens blockieren, sodass der Prozess niemals endet.
  • Wenn Sie mit ReadToEnd von StandardOutput lesen, kann Ihr -Prozess blockieren, wenn StandardOutput nie geschlossen wird (beispielsweise wenn er niemals beendet wird oder wenn das Schreiben in StandardError blockiert ist).

Die Lösung besteht darin, asynchrone Lesevorgänge zu verwenden, um sicherzustellen, dass der Puffer nicht voll wird. Um Deadlocks zu vermeiden und alle Ausgaben von StandardOutput und StandardError zu erfassen, können Sie Folgendes tun:

BEARBEITEN: In den folgenden Antworten erfahren Sie, wie Sie eine ObjectDisposedException vermeiden, wenn das Timeout auftritt.

using (Process process = new Process())
{
    process.StartInfo.FileName = filename;
    process.StartInfo.Arguments = arguments;
    process.StartInfo.UseShellExecute = false;
    process.StartInfo.RedirectStandardOutput = true;
    process.StartInfo.RedirectStandardError = true;

    StringBuilder output = new StringBuilder();
    StringBuilder error = new StringBuilder();

    using (AutoResetEvent outputWaitHandle = new AutoResetEvent(false))
    using (AutoResetEvent errorWaitHandle = new AutoResetEvent(false))
    {
        process.OutputDataReceived += (sender, e) => {
            if (e.Data == null)
            {
                outputWaitHandle.Set();
            }
            else
            {
                output.AppendLine(e.Data);
            }
        };
        process.ErrorDataReceived += (sender, e) =>
        {
            if (e.Data == null)
            {
                errorWaitHandle.Set();
            }
            else
            {
                error.AppendLine(e.Data);
            }
        };

        process.Start();

        process.BeginOutputReadLine();
        process.BeginErrorReadLine();

        if (process.WaitForExit(timeout) &&
            outputWaitHandle.WaitOne(timeout) &&
            errorWaitHandle.WaitOne(timeout))
        {
            // Process completed. Check process.ExitCode here.
        }
        else
        {
            // Timed out.
        }
    }
}
348
Mark Byers

Die Dokumentation für Process.StandardOutput sagt zu lesen, bevor Sie warten, ansonsten können Sie einen Deadlock oder einen Ausschnitt kopieren, der unten kopiert ist:

 // Start the child process.
 Process p = new Process();
 // Redirect the output stream of the child process.
 p.StartInfo.UseShellExecute = false;
 p.StartInfo.RedirectStandardOutput = true;
 p.StartInfo.FileName = "Write500Lines.exe";
 p.Start();
 // Do not wait for the child process to exit before
 // reading to the end of its redirected stream.
 // p.WaitForExit();
 // Read the output stream first and then wait.
 string output = p.StandardOutput.ReadToEnd();
 p.WaitForExit();
87
Rob

Die Antwort von Mark Byers ist ausgezeichnet, aber ich möchte nur Folgendes hinzufügen: Die Delegaten für OutputDataReceived und ErrorDataReceived müssen entfernt werden, bevor die Ausgaben outputWaitHandle und errorWaitHandle verworfen werden. Wenn der Prozess die Daten weiterhin ausgibt, nachdem das Zeitlimit überschritten wurde und dann beendet wird, wird auf die Variablen outputWaitHandle und errorWaitHandle zugegriffen, nachdem sie entsorgt wurden.

(Zu Ihrer Information: Ich musste diese Einschränkung als Antwort hinzufügen, da ich seinen Beitrag nicht kommentieren konnte.) 

18
stevejay

Das Problem mit nicht behandelter ObjectDisposedException tritt auf, wenn der Prozess das Zeitlimit überschritten hat. In diesem Fall sind die anderen Teile der Bedingung:

if (process.WaitForExit(timeout) 
    && outputWaitHandle.WaitOne(timeout) 
    && errorWaitHandle.WaitOne(timeout))

werden nicht ausgeführt. Ich habe dieses Problem auf folgende Weise gelöst:

using (AutoResetEvent outputWaitHandle = new AutoResetEvent(false))
using (AutoResetEvent errorWaitHandle = new AutoResetEvent(false))
{
    using (process = new Process())
    {
        // preparing ProcessStartInfo

        try
        {
            process.OutputDataReceived += (sender, e) =>
                {
                    if (e.Data == null)
                    {
                        outputWaitHandle.Set();
                    }
                    else
                    {
                        outputBuilder.AppendLine(e.Data);
                    }
                };
            process.ErrorDataReceived += (sender, e) =>
                {
                    if (e.Data == null)
                    {
                        errorWaitHandle.Set();
                    }
                    else
                    {
                        outputBuilder.AppendLine(e.Data);
                    }
                };

            process.Start();

            process.BeginOutputReadLine();
            process.BeginErrorReadLine();

            if (process.WaitForExit(timeout))
            {
                exitCode = process.ExitCode;
            }
            else
            {
                // timed out
            }

            output = outputBuilder.ToString();
        }
        finally
        {
            outputWaitHandle.WaitOne(timeout);
            errorWaitHandle.WaitOne(timeout);
        }
    }
}
16
Karol Tyl

Dies ist eine modernere, wartungsfähige, auf Task Parallel Library (TPL) basierende Lösung für .NET 4.5 und höher.

Verwendungsbeispiel

try
{
    var exitCode = await StartProcess(
        "dotnet", 
        "--version", 
        @"C:\",
        10000, 
        Console.Out, 
        Console.Out);
    Console.WriteLine($"Process Exited with Exit Code {exitCode}!");
}
catch (TaskCanceledException)
{
    Console.WriteLine("Process Timed Out!");
}

Implementierung

public static async Task<int> StartProcess(
    string filename,
    string arguments,
    string workingDirectory= null,
    int? timeout = null,
    TextWriter outputTextWriter = null,
    TextWriter errorTextWriter = null)
{
    using (var process = new Process()
    {
        StartInfo = new ProcessStartInfo()
        {
            CreateNoWindow = true,
            Arguments = arguments,
            FileName = filename,
            RedirectStandardOutput = outputTextWriter != null,
            RedirectStandardError = errorTextWriter != null,
            UseShellExecute = false,
            WorkingDirectory = workingDirectory
        }
    })
    {
        process.Start();
        var cancellationTokenSource = timeout.HasValue ?
            new CancellationTokenSource(timeout.Value) :
            new CancellationTokenSource();

        var tasks = new List<Task>(3) { process.WaitForExitAsync(cancellationTokenSource.Token) };
        if (outputTextWriter != null)
        {
            tasks.Add(ReadAsync(
                x =>
                {
                    process.OutputDataReceived += x;
                    process.BeginOutputReadLine();
                },
                x => process.OutputDataReceived -= x,
                outputTextWriter,
                cancellationTokenSource.Token));
        }

        if (errorTextWriter != null)
        {
            tasks.Add(ReadAsync(
                x =>
                {
                    process.ErrorDataReceived += x;
                    process.BeginErrorReadLine();
                },
                x => process.ErrorDataReceived -= x,
                errorTextWriter,
                cancellationTokenSource.Token));
        }

        await Task.WhenAll(tasks);
        return process.ExitCode;
    }
}

/// <summary>
/// Waits asynchronously for the process to exit.
/// </summary>
/// <param name="process">The process to wait for cancellation.</param>
/// <param name="cancellationToken">A cancellation token. If invoked, the task will return
/// immediately as cancelled.</param>
/// <returns>A Task representing waiting for the process to end.</returns>
public static Task WaitForExitAsync(
    this Process process,
    CancellationToken cancellationToken = default(CancellationToken))
{
    process.EnableRaisingEvents = true;

    var taskCompletionSource = new TaskCompletionSource<object>();

    EventHandler handler = null;
    handler = (sender, args) =>
    {
        process.Exited -= handler;
        taskCompletionSource.TrySetResult(null);
    };
    process.Exited += handler;

    if (cancellationToken != default(CancellationToken))
    {
        cancellationToken.Register(
            () =>
            {
                process.Exited -= handler;
                taskCompletionSource.TrySetCanceled();
            });
    }

    return taskCompletionSource.Task;
}

/// <summary>
/// Reads the data from the specified data recieved event and writes it to the
/// <paramref name="textWriter"/>.
/// </summary>
/// <param name="addHandler">Adds the event handler.</param>
/// <param name="removeHandler">Removes the event handler.</param>
/// <param name="textWriter">The text writer.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public static Task ReadAsync(
    this Action<DataReceivedEventHandler> addHandler,
    Action<DataReceivedEventHandler> removeHandler,
    TextWriter textWriter,
    CancellationToken cancellationToken = default(CancellationToken))
{
    var taskCompletionSource = new TaskCompletionSource<object>();

    DataReceivedEventHandler handler = null;
    handler = new DataReceivedEventHandler(
        (sender, e) =>
        {
            if (e.Data == null)
            {
                removeHandler(handler);
                taskCompletionSource.TrySetResult(null);
            }
            else
            {
                textWriter.WriteLine(e.Data);
            }
        });

    addHandler(handler);

    if (cancellationToken != default(CancellationToken))
    {
        cancellationToken.Register(
            () =>
            {
                removeHandler(handler);
                taskCompletionSource.TrySetCanceled();
            });
    }

    return taskCompletionSource.Task;
}
12

Rob antwortete darauf und ersparte mir noch einige Stunden Prüfungen. Lesen Sie den Ausgabe-/Fehlerpuffer, bevor Sie warten:

// Read the output stream first and then wait.
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();
7
Jon

Wir haben auch dieses Problem (oder eine Variante).

Versuche Folgendes:

1) Fügen Sie ein Timeout zu p.WaitForExit (nnnn) hinzu; wo nnnn in Millisekunden ist.

2) Setzen Sie den Aufruf von ReadToEnd vor dem Aufruf von WaitForExit. Dieses ist was wir von MS empfohlen haben.

6
torial

Gutschrift an EM0 für https://stackoverflow.com/a/17600012/4151626

Die anderen Lösungen (einschließlich EM0s) waren aufgrund interner Zeitüberschreitungen und der Verwendung von StandardOutput und StandardError durch die erzeugte Anwendung noch für meine Anwendung blockiert. Folgendes hat für mich funktioniert:

Process p = new Process()
{
  StartInfo = new ProcessStartInfo()
  {
    FileName = exe,
    Arguments = args,
    UseShellExecute = false,
    RedirectStandardOutput = true,
    RedirectStandardError = true
  }
};
p.Start();

string cv_error = null;
Thread et = new Thread(() => { cv_error = p.StandardError.ReadToEnd(); });
et.Start();

string cv_out = null;
Thread ot = new Thread(() => { cv_out = p.StandardOutput.ReadToEnd(); });
ot.Start();

p.WaitForExit();
ot.Join();
et.Join();

Edit: Initialisierung von StartInfo zum Codebeispiel hinzugefügt 

4
ergohack

Ich habe es so gelöst:

            Process proc = new Process();
            proc.StartInfo.FileName = batchFile;
            proc.StartInfo.UseShellExecute = false;
            proc.StartInfo.CreateNoWindow = true;
            proc.StartInfo.RedirectStandardError = true;
            proc.StartInfo.RedirectStandardInput = true;
            proc.StartInfo.RedirectStandardOutput = true;
            proc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;      
            proc.Start();
            StreamWriter streamWriter = proc.StandardInput;
            StreamReader outputReader = proc.StandardOutput;
            StreamReader errorReader = proc.StandardError;
            while (!outputReader.EndOfStream)
            {
                string text = outputReader.ReadLine();                    
                streamWriter.WriteLine(text);
            }

            while (!errorReader.EndOfStream)
            {                   
                string text = errorReader.ReadLine();
                streamWriter.WriteLine(text);
            }

            streamWriter.Close();
            proc.WaitForExit();

Ich habe sowohl Eingabe als auch Ausgabe und Fehler umgeleitet und das Lesen von Ausgabe- und Fehlerströmen behandelt .. Diese Lösung funktioniert für SDK 7- 8.1, sowohl für Windows 7 als auch für Windows 8 

2
Elina Maliarsky

Einführung

Die derzeit akzeptierte Antwort funktioniert nicht (löst eine Ausnahme aus) und es gibt zu viele Problemumgehungen, aber keinen vollständigen Code. Dies verschwendet offensichtlich viel Zeit, da dies eine beliebte Frage ist.

Durch die Kombination der Antwort von Mark Byers und der Antwort von Karol Tyl habe ich vollständigen Code geschrieben, der darauf basiert, wie ich die Process.Start-Methode verwenden möchte.

Verwendungszweck

Ich habe es benutzt, um Fortschrittsdialoge um Git-Befehle zu erstellen. So habe ich es benutzt:

    private bool Run(string fullCommand)
    {
        Error = "";
        int timeout = 5000;

        var result = ProcessNoBS.Start(
            filename: @"C:\Program Files\Git\cmd\git.exe",
            arguments: fullCommand,
            timeoutInMs: timeout,
            workingDir: @"C:\test");

        if (result.hasTimedOut)
        {
            Error = String.Format("Timeout ({0} sec)", timeout/1000);
            return false;
        }

        if (result.ExitCode != 0)
        {
            Error = (String.IsNullOrWhiteSpace(result.stderr)) 
                ? result.stdout : result.stderr;
            return false;
        }

        return true;
    }

Theoretisch kann man auch stdout und stderr kombinieren, aber das habe ich nicht getestet.

Code

public struct ProcessResult
{
    public string stdout;
    public string stderr;
    public bool hasTimedOut;
    private int? exitCode;

    public ProcessResult(bool hasTimedOut = true)
    {
        this.hasTimedOut = hasTimedOut;
        stdout = null;
        stderr = null;
        exitCode = null;
    }

    public int ExitCode
    {
        get 
        {
            if (hasTimedOut)
                throw new InvalidOperationException(
                    "There was no exit code - process has timed out.");

            return (int)exitCode;
        }
        set
        {
            exitCode = value;
        }
    }
}

public class ProcessNoBS
{
    public static ProcessResult Start(string filename, string arguments,
        string workingDir = null, int timeoutInMs = 5000,
        bool combineStdoutAndStderr = false)
    {
        using (AutoResetEvent outputWaitHandle = new AutoResetEvent(false))
        using (AutoResetEvent errorWaitHandle = new AutoResetEvent(false))
        {
            using (var process = new Process())
            {
                var info = new ProcessStartInfo();

                info.CreateNoWindow = true;
                info.FileName = filename;
                info.Arguments = arguments;
                info.UseShellExecute = false;
                info.RedirectStandardOutput = true;
                info.RedirectStandardError = true;

                if (workingDir != null)
                    info.WorkingDirectory = workingDir;

                process.StartInfo = info;

                StringBuilder stdout = new StringBuilder();
                StringBuilder stderr = combineStdoutAndStderr
                    ? stdout : new StringBuilder();

                var result = new ProcessResult();

                try
                {
                    process.OutputDataReceived += (sender, e) =>
                    {
                        if (e.Data == null)
                            outputWaitHandle.Set();
                        else
                            stdout.AppendLine(e.Data);
                    };
                    process.ErrorDataReceived += (sender, e) =>
                    {
                        if (e.Data == null)
                            errorWaitHandle.Set();
                        else
                            stderr.AppendLine(e.Data);
                    };

                    process.Start();

                    process.BeginOutputReadLine();
                    process.BeginErrorReadLine();

                    if (process.WaitForExit(timeoutInMs))
                        result.ExitCode = process.ExitCode;
                    // else process has timed out 
                    // but that's already default ProcessResult

                    result.stdout = stdout.ToString();
                    if (combineStdoutAndStderr)
                        result.stderr = null;
                    else
                        result.stderr = stderr.ToString();

                    return result;
                }
                finally
                {
                    outputWaitHandle.WaitOne(timeoutInMs);
                    errorWaitHandle.WaitOne(timeoutInMs);
                }
            }
        }
    }
}
1
Marko Avlijaš

Keine der obigen Antworten erledigt die Arbeit.

Rob-Lösung hängt und "Mark Byers" -Lösung erhält die entschiedene Ausnahme (ich habe die "Lösungen" der anderen Antworten ausprobiert).

Also entschied ich mich, eine andere Lösung vorzuschlagen:

public void GetProcessOutputWithTimeout(Process process, int timeoutSec, CancellationToken token, out string output, out int exitCode)
{
    string outputLocal = "";  int localExitCode = -1;
    var task = System.Threading.Tasks.Task.Factory.StartNew(() =>
    {
        outputLocal = process.StandardOutput.ReadToEnd();
        process.WaitForExit();
        localExitCode = process.ExitCode;
    }, token);

    if (task.Wait(timeoutSec, token))
    {
        output = outputLocal;
        exitCode = localExitCode;
    }
    else
    {
        exitCode = -1;
        output = "";
    }
}

using (var process = new Process())
{
    process.StartInfo = ...;
    process.Start();
    string outputUnicode; int exitCode;
    GetProcessOutputWithTimeout(process, PROCESS_TIMEOUT, out outputUnicode, out exitCode);
}

Dieser Code hat debugiert und funktioniert einwandfrei.

1
omriman12

Nachdem ich alle Beiträge hier gelesen hatte, entschied ich mich für die konsolidierte Lösung von Marko Avlijaš .Allerdings löste sich nicht alle meine Probleme.

In unserer Umgebung haben wir einen Windows-Dienst, der Hunderte verschiedener .bat .cmd .exe, ... usw.-Dateien ausführen soll, die sich im Laufe der Jahre angesammelt haben und von vielen verschiedenen Personen und in verschiedenen Stilen geschrieben wurden. Wir haben keine Kontrolle über das Schreiben der Programme und Skripte, wir sind nur für die Planung, Ausführung und Berichterstattung über Erfolg/Misserfolg verantwortlich.

Also habe ich so ziemlich alle Vorschläge mit unterschiedlichem Erfolg ausprobiert. Die Antwort von Marko war fast perfekt, aber wenn sie als Dienst ausgeführt wurde, wurden sie nicht immer als Standard definiert. Ich bin nie auf den Grund gegangen, warum nicht.

Die einzige Lösung, die wir in allen unseren Fällen gefunden haben, ist folgende: http://csharptest.net/319/using-the-processrunner-class/index.html

1
flapster

Ich denke, dass dies ein einfacher und besserer Ansatz ist (wir brauchen keine AutoResetEvent):

public static string GGSCIShell(string Path, string Command)
{
    using (Process process = new Process())
    {
        process.StartInfo.WorkingDirectory = Path;
        process.StartInfo.FileName = Path + @"\ggsci.exe";
        process.StartInfo.CreateNoWindow = true;
        process.StartInfo.RedirectStandardOutput = true;
        process.StartInfo.RedirectStandardInput = true;
        process.StartInfo.UseShellExecute = false;

        StringBuilder output = new StringBuilder();
        process.OutputDataReceived += (sender, e) =>
        {
            if (e.Data != null)
            {
                output.AppendLine(e.Data);
            }
        };

        process.Start();
        process.StandardInput.WriteLine(Command);
        process.BeginOutputReadLine();


        int timeoutParts = 10;
        int timeoutPart = (int)TIMEOUT / timeoutParts;
        do
        {
            Thread.Sleep(500);//sometimes halv scond is enough to empty output buff (therefore "exit" will be accepted without "timeoutPart" waiting)
            process.StandardInput.WriteLine("exit");
            timeoutParts--;
        }
        while (!process.WaitForExit(timeoutPart) && timeoutParts > 0);

        if (timeoutParts <= 0)
        {
            output.AppendLine("------ GGSCIShell TIMEOUT: " + TIMEOUT + "ms ------");
        }

        string result = output.ToString();
        return result;
    }
}
1
Kuzman Marinov

Dieser Beitrag ist vielleicht veraltet, aber ich habe herausgefunden, dass der Hauptgrund dafür ist, dass der Stack-Überlauf für den RedirectStandardoutput-Stapel überläuft oder wenn Sie RedirectStandarderror verwenden.

Da die Ausgangsdaten oder die Fehlerdaten groß sind, führt dies zu einer Blockierungszeit, da sie immer noch auf unbestimmte Zeit verarbeitet werden.

so lösen Sie dieses Problem:

p.StartInfo.RedirectStandardoutput = False
p.StartInfo.RedirectStandarderror = False
1
song

Ich habe versucht, eine Klasse zu erstellen, die Ihr Problem durch asynchrones Lesen von Streams löst, indem ich Mark Byers, Rob, SteveJay Antworten berücksichtigt. Dabei stellte ich fest, dass es einen Fehler beim Lesen des asynchronen Prozessausgangsstroms gibt.

Ich habe diesen Fehler bei Microsoft gemeldet: https://connect.Microsoft.com/VisualStudio/feedback/details/3119134

Zusammenfassung:

Das kannst du nicht machen:

process.BeginOutputReadLine (); Prozess.Start ();

Sie erhalten System.InvalidOperationException: StandardOut hat wurde nicht umgeleitet oder der Prozess wurde noch nicht gestartet.

================================================== ================================================== ==========================

Dann müssen Sie mit dem asynchronen Lesen der Ausgabe beginnen, nachdem der Prozess .__ ist. gestartet:

prozess.Start (); process.BeginOutputReadLine ();

Machen Sie eine Race-Bedingung, da der Ausgabestrom .__ empfangen kann. Daten, bevor Sie es asynchron einstellen:

process.Start(); 
// Here the operating system could give the cpu to another thread.  
// For example, the newly created thread (Process) and it could start writing to the output
// immediately before next line would execute. 
// That create a race condition.
process.BeginOutputReadLine();

================================================== ================================================== ==========================

Dann könnten einige Leute sagen, dass Sie nur den Stream lesen müssen bevor Sie es asynchron einstellen. Aber das gleiche Problem tritt auf. Dort wird eine Wettlaufsituation zwischen dem synchronen Lesen und dem Setzen der .__ sein. Stream in asynchronem Modus.

================================================== ================================================== ==========================

Es gibt keine Möglichkeit, ein sicheres asynchrones Lesen eines Ausgabestroms zu erreichen eines Prozesses auf die tatsächliche Art und Weise, wie "Process" und "ProcessStartInfo" entworfen worden.

Sie verwenden wahrscheinlich asynchrones Lesen, wie von anderen Benutzern für Ihren Fall vorgeschlagen. Sie sollten sich jedoch darüber im Klaren sein, dass Sie aufgrund von Rennbedingungen einige Informationen verpassen könnten.

1
Eric Ouellet

Problemumgehung, die ich letztendlich verwendet habe, um die gesamte Komplexität zu vermeiden:

var outputFile = Path.GetTempFileName();
info = new System.Diagnostics.ProcessStartInfo("TheProgram.exe", String.Join(" ", args) + " > " + outputFile + " 2>&1");
info.CreateNoWindow = true;
info.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
info.UseShellExecute = false;
System.Diagnostics.Process p = System.Diagnostics.Process.Start(info);
p.WaitForExit();
Console.WriteLine(File.ReadAllText(outputFile)); //need the StandardOutput contents

Also erstelle ich eine temporäre Datei, leite sowohl die Ausgabe als auch den Fehler mit > outputfile > 2>&1 dorthin um und lese die Datei erst dann, wenn der Vorgang abgeschlossen ist.

Die anderen Lösungen eignen sich gut für Szenarien, in denen Sie andere Aufgaben mit der Ausgabe ausführen möchten. Bei einfachen Aufgaben wird jedoch viel Komplexität vermieden.

1
eglasius

Ich weiß, dass dies Abendbrot ist, aber nachdem ich diese ganze Seite gelesen hatte, funktionierte keine der Lösungen für mich, obwohl ich Muhammad Rehan nicht ausprobierte, da der Code ein wenig schwer zu befolgen war, obwohl er auf dem richtigen Weg war . Wenn ich sage, dass es nicht funktioniert hat, ist das nicht ganz richtig, manchmal funktioniert es gut. Ich denke, es hat etwas mit der Länge der Ausgabe vor einer EOF Markierung zu tun.

Wie dem auch sei, die Lösung, die für mich funktioniert hat, war, verschiedene Threads zu verwenden, um StandardOutput und StandardError zu lesen und die Meldungen zu schreiben.

        StreamWriter sw = null;
        var queue = new ConcurrentQueue<string>();

        var flushTask = new System.Timers.Timer(50);
        flushTask.Elapsed += (s, e) =>
        {
            while (!queue.IsEmpty)
            {
                string line = null;
                if (queue.TryDequeue(out line))
                    sw.WriteLine(line);
            }
            sw.FlushAsync();
        };
        flushTask.Start();

        using (var process = new Process())
        {
            try
            {
                process.StartInfo.FileName = @"...";
                process.StartInfo.Arguments = $"...";
                process.StartInfo.UseShellExecute = false;
                process.StartInfo.RedirectStandardOutput = true;
                process.StartInfo.RedirectStandardError = true;

                process.Start();

                var outputRead = Task.Run(() =>
                {
                    while (!process.StandardOutput.EndOfStream)
                    {
                        queue.Enqueue(process.StandardOutput.ReadLine());
                    }
                });

                var errorRead = Task.Run(() =>
                {
                    while (!process.StandardError.EndOfStream)
                    {
                        queue.Enqueue(process.StandardError.ReadLine());
                    }
                });

                var timeout = new TimeSpan(hours: 0, minutes: 10, seconds: 0);

                if (Task.WaitAll(new[] { outputRead, errorRead }, timeout) &&
                    process.WaitForExit((int)timeout.TotalMilliseconds))
                {
                    if (process.ExitCode != 0)
                    {
                        throw new Exception($"Failed run... blah blah");
                    }
                }
                else
                {
                    throw new Exception($"process timed out after waiting {timeout}");
                }
            }
            catch (Exception e)
            {
                throw new Exception($"Failed to succesfully run the process.....", e);
            }
        }
    }

Hoffe, das hilft jemandem, der dachte, das könnte so schwer sein!

1
Alexis Coles

Nennen wir den hier veröffentlichten Beispielcode den Redirector und das andere Programm das umgeleitete. Wenn ich es wäre, würde ich wahrscheinlich ein Testumleitungsprogramm schreiben, mit dem das Problem dupliziert werden kann.

So tat ich. Für Testdaten habe ich die ECMA-334 C # -Sprachenspezifikationv PDF verwendet; es geht um 5 MB. Das Folgende ist der wichtige Teil davon.

StreamReader stream = null;
try { stream = new StreamReader(Path); }
catch (Exception ex)
{
    Console.Error.WriteLine("Input open error: " + ex.Message);
    return;
}
Console.SetIn(stream);
int datasize = 0;
try
{
    string record = Console.ReadLine();
    while (record != null)
    {
        datasize += record.Length + 2;
        record = Console.ReadLine();
        Console.WriteLine(record);
    }
}
catch (Exception ex)
{
    Console.Error.WriteLine($"Error: {ex.Message}");
    return;
}

Der Wert für die Datengröße stimmt nicht mit der tatsächlichen Dateigröße überein, aber das spielt keine Rolle. Es ist nicht klar, ob in einer PDF -Datei immer sowohl CR als auch LF am Zeilenende verwendet werden. Dies ist jedoch unerheblich. Sie können jede andere große Textdatei zum Testen verwenden.

Der Beispiel-Redirector-Code hängt davon ab, wenn ich die große Datenmenge schreibe, nicht aber, wenn ich eine kleine Menge schreibe.

Ich habe sehr versucht, die Ausführung dieses Codes irgendwie zu verfolgen, und ich konnte es nicht. Ich habe die Zeilen des umgeleiteten Programms auskommentiert, das die Erstellung einer Konsole für das umgeleitete Programm deaktiviert hat, um zu versuchen, ein separates Konsolenfenster zu erhalten, aber ich konnte es nicht.

Dann fand ich Wie starte ich eine Konsolen-App in einem neuen Fenster, dem Fenster der Eltern oder ohne Fenster . Anscheinend können wir (leicht) keine separate Konsole haben, wenn ein Konsolenprogramm ein anderes Konsolenprogramm ohne ShellExecute startet und da ShellExecute keine Umleitung unterstützt, müssen wir eine Konsole freigeben, auch wenn wir kein Fenster für den anderen Prozess angeben.

Ich gehe davon aus, dass das umgeleitete Programm, wenn es irgendwo einen Puffer auffüllt, warten muss, bis die Daten gelesen sind. Wenn an diesem Punkt keine Daten vom Redirector gelesen werden, handelt es sich um einen Deadlock.

Die Lösung besteht darin, ReadToEnd nicht zu verwenden und die Daten zu lesen, während die Daten geschrieben werden, aber es ist nicht erforderlich, asynchrone Lesevorgänge zu verwenden. Die Lösung kann ganz einfach sein. Folgendes funktioniert für mich mit dem 5 MB PDF.

ProcessStartInfo info = new ProcessStartInfo(TheProgram);
info.CreateNoWindow = true;
info.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
info.RedirectStandardOutput = true;
info.UseShellExecute = false;
Process p = Process.Start(info);
string record = p.StandardOutput.ReadLine();
while (record != null)
{
    Console.WriteLine(record);
    record = p.StandardOutput.ReadLine();
}
p.WaitForExit();

Eine andere Möglichkeit besteht darin, ein GUI-Programm für die Umleitung zu verwenden. Der vorhergehende Code funktioniert in einer WPF-Anwendung, mit offensichtlichen Änderungen.

0
user34660

Ich denke, mit async ist es möglich, eine elegantere Lösung zu haben und selbst bei Verwendung von standardOutput und standardError keine Deadlocks zu haben:

using (Process process = new Process())
{
    process.StartInfo.FileName = filename;
    process.StartInfo.Arguments = arguments;
    process.StartInfo.UseShellExecute = false;
    process.StartInfo.RedirectStandardOutput = true;
    process.StartInfo.RedirectStandardError = true;

    process.Start();

    var tStandardOutput = process.StandardOutput.ReadToEndAsync();
    var tStandardError = process.StandardError.ReadToEndAsync();

    if (process.WaitForExit(timeout))
    {
        string output = await tStandardOutput;
        string errors = await tStandardError;

        // Process completed. Check process.ExitCode here.
    }
    else
    {
        // Timed out.
    }
}

Es basiert auf der Antwort von Mark Byers . Wenn Sie sich nicht in einer asynchronen Methode befinden, können Sie string output = tStandardOutput.result; anstelle von await verwenden

0
Yepeekai