Der Prozess hängt manchmal beim Warten auf das Beenden

13

Was kann der Grund dafür sein, dass mein Prozess hängt, während ich auf den Ausgang warte?

Dieser Code muss ein Powershell-Skript starten, das viele Aktionen ausführt, z. B. das Neukompilieren von Code über MSBuild. Das Problem besteht jedoch wahrscheinlich darin, dass zu viel Ausgabe generiert wird und dieser Code auch nach korrekter Ausführung des Power Shell-Skripts beim Beenden hängen bleibt

Es ist ein bisschen "komisch", weil dieser Code manchmal gut funktioniert und manchmal einfach hängen bleibt.

Code hängt an:

process.WaitForExit (ProcessTimeOutMiliseconds);

Das Powershell-Skript wird in 1-2 Sekunden ausgeführt, während das Zeitlimit 19 Sekunden beträgt.

public static (bool Success, string Logs) ExecuteScript(string path, int ProcessTimeOutMiliseconds, params string[] args)
{
    StringBuilder output = new StringBuilder();
    StringBuilder error = new StringBuilder();

    using (var outputWaitHandle = new AutoResetEvent(false))
    using (var errorWaitHandle = new AutoResetEvent(false))
    {
        try
        {
            using (var process = new Process())
            {
                process.StartInfo = new ProcessStartInfo
                {
                    WindowStyle = ProcessWindowStyle.Hidden,
                    FileName = "powershell.exe",
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                    UseShellExecute = false,
                    Arguments = $"-ExecutionPolicy Bypass -File \"{path}\"",
                    WorkingDirectory = Path.GetDirectoryName(path)
                };

                if (args.Length > 0)
                {
                    var arguments = string.Join(" ", args.Select(x => $"\"{x}\""));
                    process.StartInfo.Arguments += $" {arguments}";
                }

                output.AppendLine($"args:'{process.StartInfo.Arguments}'");

                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();

                process.WaitForExit(ProcessTimeOutMiliseconds);

                var logs = output + Environment.NewLine + error;

                return process.ExitCode == 0 ? (true, logs) : (false, logs);
            }
        }
        finally
        {
            outputWaitHandle.WaitOne(ProcessTimeOutMiliseconds);
            errorWaitHandle.WaitOne(ProcessTimeOutMiliseconds);
        }
    }
}

Skript:

start-process $args[0] App.csproj -Wait -NoNewWindow

[string]$sourceDirectory  = "\bin\Debug\*"
[int]$count = (dir $sourceDirectory | measure).Count;

If ($count -eq 0)
{
    exit 1;
}
Else
{
    exit 0;
}

wo

$args[0] = "C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\MSBuild\Current\Bin\MSBuild.exe"

Bearbeiten

Zu @ ingens Lösung habe ich einen kleinen Wrapper hinzugefügt, der erneut versucht, aufgehängtes MS Build auszuführen

public static void ExecuteScriptRx(string path, int processTimeOutMilliseconds, out string logs, out bool success, params string[] args)
{
    var current = 0;
    int attempts_count = 5;
    bool _local_success = false;
    string _local_logs = "";

    while (attempts_count > 0 && _local_success == false)
    {
        Console.WriteLine($"Attempt: {++current}");
        InternalExecuteScript(path, processTimeOutMilliseconds, out _local_logs, out _local_success, args);
        attempts_count--;
    }

    success = _local_success;
    logs = _local_logs;
}

Wo InternalExecuteScriptist der Code von ingen

Joelty
quelle
an welcher Zeile hängt eigentlich der Prozess? und führen Sie Ihren Code viel mehr ein
Mr.AF
@ Mr.AF du hast recht - fertig.
Joelty
1
Der eigentliche Aufruf von Powershell ist eine Sache. Was Sie jedoch NICHT bereitstellen, ist der eigentliche Rest des Skripts, das Sie während WITHIN Powershell verarbeiten möchten. Powershell selbst aufzurufen ist nicht das Problem, sondern das, was Sie versuchen zu tun. Bearbeiten Sie Ihren Beitrag und geben Sie den expliziten Aufruf / die expliziten Befehle ein, die Sie ausführen möchten.
DRapp
1
Es ist wirklich komisch, dass ich versucht habe, den Fehler zu replizieren. Es passierte zufällig zweimal bei 20 Versuchen oder so und ich kann es nicht wieder auslösen.
KiKoS
1
@Joelty, ohh cool interessant, sagst du, dass der RxAnsatz funktioniert hat (da es kein Timeout gab), selbst wenn ein streunender MSBuild-Prozess herumlag und zu unbestimmtem Warten führte? interessiert zu wissen, wie das gehandhabt wurde
Clint

Antworten:

9

Beginnen wir mit einer Zusammenfassung der akzeptierten Antwort in einem verwandten Beitrag.

Das Problem ist, dass der interne Puffer voll werden kann, wenn Sie StandardOutput und / oder StandardError umleiten. Unabhängig von der Reihenfolge, die Sie verwenden, kann ein Problem auftreten:

  • Wenn Sie warten, bis der Prozess beendet ist, bevor Sie StandardOutput lesen, kann der Prozess den Versuch blockieren, darauf zu schreiben, sodass der Prozess niemals endet.
  • Wenn Sie mit ReadToEnd aus StandardOutput lesen, kann Ihr Prozess blockieren, wenn der Prozess StandardOutput nie schließt (z. B. wenn er nie beendet wird oder wenn das Schreiben in StandardError blockiert ist).

Selbst die akzeptierte Antwort hat jedoch in bestimmten Fällen Probleme mit der Reihenfolge der Ausführung.

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

In solchen Situationen, in denen Sie mehrere Ereignisse orchestrieren möchten, strahlt Rx wirklich.

Beachten Sie, dass die .NET-Implementierung von Rx als System.Reactive NuGet-Paket verfügbar ist.

Lassen Sie uns einen Blick darauf werfen, wie Rx die Arbeit mit Ereignissen erleichtert.

// Subscribe to OutputData
Observable.FromEventPattern<DataReceivedEventArgs>(process, nameof(Process.OutputDataReceived))
    .Subscribe(
        eventPattern => output.AppendLine(eventPattern.EventArgs.Data),
        exception => error.AppendLine(exception.Message)
    ).DisposeWith(disposables);

FromEventPatternErmöglicht es uns, verschiedene Ereignisse eines Ereignisses einem einheitlichen Stream (auch als beobachtbar bezeichnet) zuzuordnen. Dies ermöglicht es uns, die Ereignisse in einer Pipeline zu behandeln (mit LINQ-ähnlicher Semantik). Die Subscribehier verwendete Überlast ist mit einem Action<EventPattern<...>>und einem versehen Action<Exception>. Jedes Mal , wenn das beobachtete Ereignis ausgelöst wird, sein senderund argswird durch eingewickelt werden EventPatternund durch die geschoben Action<EventPattern<...>>. Wenn eine Ausnahme in der Pipeline ausgelöst wird, Action<Exception>wird verwendet.

Einer der Nachteile des EventMusters, der in diesem Anwendungsfall (und durch alle Problemumgehungen im referenzierten Beitrag) deutlich dargestellt wird, besteht darin, dass nicht ersichtlich ist, wann / wo die Ereignishandler abgemeldet werden müssen.

Mit Rx erhalten wir eine zurück, IDisposablewenn wir ein Abonnement abschließen. Wenn wir darüber verfügen, beenden wir das Abonnement effektiv. Mit der Hinzufügung der DisposeWithErweiterungsmethode (von RxUI ausgeliehen ) können wir IDisposableeinem CompositeDisposable( disposablesin den Codebeispielen genannten) mehrere s hinzufügen . Wenn wir alle fertig sind, können wir alle Abonnements mit einem Anruf beenden disposables.Dispose().

Natürlich können wir mit Rx nichts tun, was wir mit Vanilla .NET nicht tun könnten. Der resultierende Code ist viel einfacher zu überlegen, sobald Sie sich an die funktionale Denkweise angepasst haben.

public static void ExecuteScriptRx(string path, int processTimeOutMilliseconds, out string logs, out bool success, params string[] args)
{
    StringBuilder output = new StringBuilder();
    StringBuilder error = new StringBuilder();

    using (var process = new Process())
    using (var disposables = new CompositeDisposable())
    {
        process.StartInfo = new ProcessStartInfo
        {
            WindowStyle = ProcessWindowStyle.Hidden,
            FileName = "powershell.exe",
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            UseShellExecute = false,
            Arguments = $"-ExecutionPolicy Bypass -File \"{path}\"",
            WorkingDirectory = Path.GetDirectoryName(path)
        };

        if (args.Length > 0)
        {
            var arguments = string.Join(" ", args.Select(x => $"\"{x}\""));
            process.StartInfo.Arguments += $" {arguments}";
        }

        output.AppendLine($"args:'{process.StartInfo.Arguments}'");

        // Raise the Process.Exited event when the process terminates.
        process.EnableRaisingEvents = true;

        // Subscribe to OutputData
        Observable.FromEventPattern<DataReceivedEventArgs>(process, nameof(Process.OutputDataReceived))
            .Subscribe(
                eventPattern => output.AppendLine(eventPattern.EventArgs.Data),
                exception => error.AppendLine(exception.Message)
            ).DisposeWith(disposables);

        // Subscribe to ErrorData
        Observable.FromEventPattern<DataReceivedEventArgs>(process, nameof(Process.ErrorDataReceived))
            .Subscribe(
                eventPattern => error.AppendLine(eventPattern.EventArgs.Data),
                exception => error.AppendLine(exception.Message)
            ).DisposeWith(disposables);

        var processExited =
            // Observable will tick when the process has gracefully exited.
            Observable.FromEventPattern<EventArgs>(process, nameof(Process.Exited))
                // First two lines to tick true when the process has gracefully exited and false when it has timed out.
                .Select(_ => true)
                .Timeout(TimeSpan.FromMilliseconds(processTimeOutMilliseconds), Observable.Return(false))
                // Force termination when the process timed out
                .Do(exitedSuccessfully => { if (!exitedSuccessfully) { try { process.Kill(); } catch {} } } );

        // Subscribe to the Process.Exited event.
        processExited
            .Subscribe()
            .DisposeWith(disposables);

        // Start process(ing)
        process.Start();

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

        // Wait for the process to terminate (gracefully or forced)
        processExited.Take(1).Wait();

        logs = output + Environment.NewLine + error;
        success = process.ExitCode == 0;
    }
}

Wir haben bereits den ersten Teil besprochen, in dem wir unsere Ereignisse Observablen zuordnen, damit wir direkt zum fleischigen Teil springen können. Hier weisen wir der processExitedVariablen unser Observable zu , weil wir es mehr als einmal verwenden möchten.

Erstens, wenn wir es aktivieren, indem wir anrufen Subscribe. Und später, wenn wir auf seinen ersten Wert warten wollen.

var processExited =
    // Observable will tick when the process has gracefully exited.
    Observable.FromEventPattern<EventArgs>(process, nameof(Process.Exited))
        // First two lines to tick true when the process has gracefully exited and false when it has timed out.
        .Select(_ => true)
        .Timeout(TimeSpan.FromMilliseconds(processTimeOutMilliseconds), Observable.Return(false))
        // Force termination when the process timed out
        .Do(exitedSuccessfully => { if (!exitedSuccessfully) { try { process.Kill(); } catch {} } } );

// Subscribe to the Process.Exited event.
processExited
    .Subscribe()
    .DisposeWith(disposables);

// Start process(ing)
...

// Wait for the process to terminate (gracefully or forced)
processExited.Take(1).Wait();

Eines der Probleme mit OP besteht darin, dass davon process.WaitForExit(processTimeOutMiliseconds)ausgegangen wird, dass der Prozess beendet wird, wenn das Zeitlimit überschritten wird. Von MSDN :

Weist die Prozesskomponente an, die angegebene Anzahl von Millisekunden auf das Beenden des zugeordneten Prozesses zu warten.

Wenn das Zeitlimit überschritten wird, wird lediglich die Kontrolle an den aktuellen Thread zurückgegeben (dh es wird nicht mehr blockiert). Sie müssen die Beendigung manuell erzwingen, wenn der Prozess abläuft. Um zu wissen, wann Process.Exitedeine processExitedZeitüberschreitung aufgetreten ist, können wir das Ereignis einem Observable zur Verarbeitung zuordnen. Auf diese Weise können wir die Eingabe für den DoBediener vorbereiten .

Der Code ist ziemlich selbsterklärend. Wenn exitedSuccessfullyder Prozess ordnungsgemäß beendet wurde. Wenn nicht exitedSuccessfully, muss die Kündigung erzwungen werden. Beachten Sie, dass process.Kill()asynchron ausgeführt wird, siehe Anmerkungen . Wenn Sie jedoch process.WaitForExit()direkt danach anrufen, besteht erneut die Möglichkeit von Deadlocks. Selbst im Fall einer erzwungenen Kündigung ist es besser, alle Einwegartikel nach Ablauf des usingGültigkeitsbereichs bereinigen zu lassen , da die Ausgabe ohnehin als unterbrochen / beschädigt angesehen werden kann.

Das try catchKonstrukt ist für den Ausnahmefall reserviert (kein Wortspiel beabsichtigt), in dem Sie sich an processTimeOutMillisecondsder tatsächlichen Zeit ausgerichtet haben, die der Prozess zum Abschließen benötigt. Mit anderen Worten, zwischen dem Process.ExitedEreignis und dem Timer tritt eine Rennbedingung auf . Die Möglichkeit, dass dies geschieht, wird erneut durch die asynchrone Natur von vergrößert process.Kill(). Ich habe es einmal beim Testen angetroffen.


Der Vollständigkeit halber die DisposeWithErweiterungsmethode.

/// <summary>
/// Extension methods associated with the IDisposable interface.
/// </summary>
public static class DisposableExtensions
{
    /// <summary>
    /// Ensures the provided disposable is disposed with the specified <see cref="CompositeDisposable"/>.
    /// </summary>
    public static T DisposeWith<T>(this T item, CompositeDisposable compositeDisposable)
        where T : IDisposable
    {
        if (compositeDisposable == null)
        {
            throw new ArgumentNullException(nameof(compositeDisposable));
        }

        compositeDisposable.Add(item);
        return item;
    }
}
ingen
quelle
4
IMHO, definitiv das Kopfgeld wert. Gute Antwort und nette Einführung in RX zum Thema.
Quetzalcoatl
Vielen Dank!!! Ihre ExecuteScriptRxGriffe hangsperfekt. Leider kommt es immer noch zu Hängen, aber ich habe gerade einen kleinen Wrapper über Ihre Leistung hinzugefügt ExecuteScriptRx, Retryund dann wird sie einwandfrei ausgeführt. Grund für MSBUILD-Hänge ist möglicherweise die @ Clint-Antwort. PS: Dieser Code hat mich dumm gemacht <lol> Das ist das erste Mal, dass ich seheSystem.Reactive.Linq;
Joelty
Wrappers Code ist im Hauptbeitrag
Joelty
3

Zum Wohle der Leser werde ich dies in zwei Abschnitte unterteilen

Abschnitt A: Problem und Umgang mit ähnlichen Szenarien

Abschnitt B: Problemwiederherstellung und -lösung

Abschnitt A: Problem

Wenn dieses Problem auftritt, wird der Prozess im Task-Manager angezeigt. Nachdem 2-3 Sekunden verschwunden sind (in Ordnung), wartet er auf eine Zeitüberschreitung und die Ausnahme wird ausgelöst. System.InvalidOperationException: Der Prozess muss beendet werden, bevor die angeforderten Informationen ermittelt werden können.

& Siehe Szenario 4 unten

In Ihrem Code:

  1. Process.WaitForExit(ProcessTimeOutMiliseconds); Damit warten Sie Processauf Timeout oder Exit , was immer zuerst stattfindet .
  2. OutputWaitHandle.WaitOne(ProcessTimeOutMiliseconds)und errorWaitHandle.WaitOne(ProcessTimeOutMiliseconds); Damit warten Sie auf den OutputData& ErrorDataStream-Lesevorgang, um den Abschluss zu signalisieren
  3. Process.ExitCode == 0 Ruft den Status des Prozesses ab, wenn er beendet wird

Unterschiedliche Einstellungen und deren Einschränkungen:

  • Szenario 1 (Happy Path) : Der Prozess wird vor dem Timeout abgeschlossen, und daher werden Ihre Standardausgabe und Ihr Stderror auch davor beendet, und alles ist in Ordnung.
  • Szenario 2 : Prozess-, OutputWaitHandle- und ErrorWaitHandle-Zeitlimit, jedoch wird stdoutput & stderror noch gelesen und nach Ablauf des Zeitlimits für WaitHandlers abgeschlossen. Dies führt zu einer weiteren AusnahmeObjectDisposedException()
  • Szenario 3 : Verarbeiten Sie zuerst eine Zeitüberschreitung (19 Sekunden), aber stdout und stderror sind in Aktion. Warten Sie, bis WaitHandler eine Zeitüberschreitung aufweist (19 Sekunden), was zu einer zusätzlichen Verzögerung von + 19 Sekunden führt.
  • Szenario 4 : Prozess läuft ab und Code versucht, vorzeitig abzufragen, Process.ExitCodewas zu dem Fehler führt System.InvalidOperationException: Process must exit before requested information can be determined.

Ich habe dieses Szenario über ein Dutzend Mal getestet und funktioniert einwandfrei. Die folgenden Einstellungen wurden beim Testen verwendet

  • Die Größe des Ausgabestreams reicht von 5 KB bis 198 KB, indem der Build von ca. 2-15 Projekten initiiert wird
  • Vorzeitige Zeitüberschreitungen und Prozessabbrüche innerhalb des Zeitüberschreitungsfensters


Code aktualisiert

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

    //First waiting for ReadOperations to Timeout and then check Process to Timeout
    if (!outputWaitHandle.WaitOne(ProcessTimeOutMiliseconds) && !errorWaitHandle.WaitOne(ProcessTimeOutMiliseconds)
        && !process.WaitForExit(ProcessTimeOutMiliseconds)  )
    {
        //To cancel the Read operation if the process is stil reading after the timeout this will prevent ObjectDisposeException
        process.CancelOutputRead();
        process.CancelErrorRead();

        Console.ForegroundColor = ConsoleColor.Red;
        Console.WriteLine("Timed Out");
        Logs = output + Environment.NewLine + error;
       //To release allocated resource for the Process
        process.Close();
        return  (false, logs);
    }

    Console.ForegroundColor = ConsoleColor.Green;
    Console.WriteLine("Completed On Time");
    Logs = output + Environment.NewLine + error;
    ExitCode = process.ExitCode.ToString();
    // Close frees the memory allocated to the exited process
    process.Close();

    //ExitCode now accessible
    return process.ExitCode == 0 ? (true, logs) : (false, logs);
    }
}
finally{}

BEARBEITEN:

Nach stundenlangem Herumspielen mit MSBuild konnte ich das Problem endlich auf meinem System reproduzieren


Abschnitt B: Problemwiederherstellung und -lösung

MSBuild verfügt über einen-m[:number]Schalter, mit dem die maximale Anzahl gleichzeitiger Prozesse angegeben wird, die beim Erstellen verwendet werden sollen.

Wenn dies aktiviert ist, erzeugt MSBuild eine Reihe von Knoten, die auch nach Abschluss des Builds weiterleben. Jetzt Process.WaitForExit(milliseconds)würde warten nie beenden und schließlich Timeout

Ich konnte dies auf verschiedene Arten lösen

  • Spawn MSBuild-Prozess indirekt über CMD

    $path1 = """C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\MSBuild.exe"" ""C:\Users\John\source\repos\Test\Test.sln"" -maxcpucount:3"
    $cmdOutput = cmd.exe /c $path1  '2>&1'
    $cmdOutput
  • Verwenden Sie MSBuild weiterhin, stellen Sie jedoch sicher, dass nodeReuse auf False gesetzt ist

    $filepath = "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\MSBuild.exe"
    $arg1 = "C:\Users\John\source\repos\Test\Test.sln"
    $arg2 = "-m:3"
    $arg3 = "-nr:False"
    
    Start-Process -FilePath $filepath -ArgumentList $arg1,$arg2,$arg3 -Wait -NoNewWindow
  • Selbst wenn die parallele Erstellung nicht aktiviert ist, können Sie verhindern, dass Ihr Prozess hängen bleibt, WaitForExitindem Sie die Erstellung über CMD starten. Daher erstellen Sie keine direkte Abhängigkeit vom Erstellungsprozess

    $path1 = """C:\....\15.0\Bin\MSBuild.exe"" ""C:\Users\John\source\Test.sln"""
    $cmdOutput = cmd.exe /c $path1  '2>&1'
    $cmdOutput

Der zweite Ansatz wird bevorzugt, da nicht zu viele MSBuild-Knoten herumliegen sollen.

Clint
quelle
Also, wie ich oben sagte, danke, dies "-nr:False","-m:3"scheint das MSBuild-Hang-ish-Verhalten behoben zu haben, was Rx solutionden gesamten Prozess einigermaßen zuverlässig gemacht hat (die Zeit wird es zeigen). Ich wünschte, ich könnte beide Antworten akzeptieren oder zwei Kopfgelder geben
Joelty
@Joelty Ich habe nur versucht zu wissen, ob der RxAnsatz in der anderen Lösung das Problem lösen kann, ohne sich zu bewerben -nr:False" ,"-m:3". Nach meinem Verständnis behandelt es unbestimmte Wartezeiten aufgrund von Deadlocks und anderen Dingen, die ich in Abschnitt 1 behandelt habe. Und die Grundursache in Abschnitt 2 ist meiner Meinung nach die Hauptursache für das Problem, mit dem Sie konfrontiert waren;) Ich kann mich irren, weshalb Ich fragte, nur die Zeit wird es zeigen ... Prost !!
Clint
3

Das Problem ist, dass der interne Puffer voll werden kann, wenn Sie StandardOutput und / oder StandardError umleiten.

Um die oben genannten Probleme zu lösen, können Sie den Prozess in separaten Threads ausführen. Ich verwende WaitForExit nicht. Ich verwende das Ereignis "Prozess beendet", das den ExitCode des Prozesses asynchron zurückgibt, um sicherzustellen, dass er abgeschlossen wurde.

public async Task<int> RunProcessAsync(params string[] args)
    {
        try
        {
            var tcs = new TaskCompletionSource<int>();

            var process = new Process
            {
                StartInfo = {
                    FileName = 'file path',
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                    Arguments = "shell command",
                    UseShellExecute = false,
                    CreateNoWindow = true
                },
                EnableRaisingEvents = true
            };


            process.Exited += (sender, args) =>
            {
                tcs.SetResult(process.ExitCode);
                process.Dispose();
            };

            process.Start();
            // Use asynchronous read operations on at least one of the streams.
            // Reading both streams synchronously would generate another deadlock.
            process.BeginOutputReadLine();
            string tmpErrorOut = await process.StandardError.ReadToEndAsync();
            //process.WaitForExit();


            return await tcs.Task;
        }
        catch (Exception ee) {
            Console.WriteLine(ee.Message);
        }
        return -1;
    }

Der obige Code wurde im Kampf getestet, indem FFMPEG.exe mit Befehlszeilenargumenten aufgerufen wurde. Ich habe MP4-Dateien in MP3-Dateien konvertiert und über 1000 Videos gleichzeitig erstellt, ohne dass dies fehlschlug. Leider habe ich keine direkte Power Shell Erfahrung, hoffe aber, dass dies hilft.

Alex
quelle
Es ist seltsam, dass dieser Code, ähnlich wie bei anderen Lösungen, beim ERSTEN Versuch fehlgeschlagen ist und dann gut funktioniert hat (wie bei anderen 5 Versuchen werde ich ihn mehr testen). Btw , warum führen Sie BegingOutputReadlineund führen Sie dann ReadToEndAsyncauf StandardError?
Joelty
OP liest bereits asynchron, daher ist es unwahrscheinlich, dass hier ein Deadlock im Konsolenpuffer auftritt.
Yaakov
0

Ich bin mir nicht sicher, ob dies Ihr Problem ist, aber wenn Sie sich MSDN ansehen, scheint der überladene WaitForExit etwas seltsam zu sein, wenn Sie die Ausgabe asynchron umleiten. In einem MSDN-Artikel wird empfohlen, den WaitForExit aufzurufen, der nach dem Aufruf der überladenen Methode keine Argumente akzeptiert.

Die Docs-Seite befindet sich hier. Relevanter Text:

Wenn die Standardausgabe an asynchrone Ereignishandler umgeleitet wurde, ist die Ausgabeverarbeitung möglicherweise nicht abgeschlossen, wenn diese Methode zurückgegeben wird. Um sicherzustellen, dass die asynchrone Ereignisbehandlung abgeschlossen ist, rufen Sie die WaitForExit () - Überladung auf, die nach dem Empfang eines true von dieser Überladung keinen Parameter akzeptiert. Legen Sie die SynchronizingObject-Eigenschaft fest, um sicherzustellen, dass das Ereignis "Beendet" in Windows Forms-Anwendungen ordnungsgemäß behandelt wird.

Die Codeänderung könnte ungefähr so ​​aussehen:

if (process.WaitForExit(ProcessTimeOutMiliseconds))
{
  process.WaitForExit();
}
Tyler Hundley
quelle
Es gibt einige Komplikationen bei der Verwendung von, process.WaitForExit()wie in den Kommentaren zu dieser Antwort angegeben .
Ingen