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 InternalExecuteScript
ist der Code von ingen
Rx
Ansatz 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 wurdeAntworten:
Beginnen wir mit einer Zusammenfassung der akzeptierten Antwort in einem verwandten Beitrag.
Selbst die akzeptierte Antwort hat jedoch in bestimmten Fällen Probleme mit der Reihenfolge der Ausführung.
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.
FromEventPattern
Ermö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). DieSubscribe
hier verwendete Überlast ist mit einemAction<EventPattern<...>>
und einem versehenAction<Exception>
. Jedes Mal , wenn das beobachtete Ereignis ausgelöst wird, seinsender
undargs
wird durch eingewickelt werdenEventPattern
und durch die geschobenAction<EventPattern<...>>
. Wenn eine Ausnahme in der Pipeline ausgelöst wird,Action<Exception>
wird verwendet.Einer der Nachteile des
Event
Musters, 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,
IDisposable
wenn wir ein Abonnement abschließen. Wenn wir darüber verfügen, beenden wir das Abonnement effektiv. Mit der Hinzufügung derDisposeWith
Erweiterungsmethode (von RxUI ausgeliehen ) können wirIDisposable
einemCompositeDisposable
(disposables
in den Codebeispielen genannten) mehrere s hinzufügen . Wenn wir alle fertig sind, können wir alle Abonnements mit einem Anruf beendendisposables.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.
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
processExited
Variablen 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.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 :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.Exited
eineprocessExited
Zeitüberschreitung aufgetreten ist, können wir das Ereignis einem Observable zur Verarbeitung zuordnen. Auf diese Weise können wir die Eingabe für denDo
Bediener vorbereiten .Der Code ist ziemlich selbsterklärend. Wenn
exitedSuccessfully
der Prozess ordnungsgemäß beendet wurde. Wenn nichtexitedSuccessfully
, muss die Kündigung erzwungen werden. Beachten Sie, dassprocess.Kill()
asynchron ausgeführt wird, siehe Anmerkungen . Wenn Sie jedochprocess.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 desusing
Gültigkeitsbereichs bereinigen zu lassen , da die Ausgabe ohnehin als unterbrochen / beschädigt angesehen werden kann.Das
try catch
Konstrukt ist für den Ausnahmefall reserviert (kein Wortspiel beabsichtigt), in dem Sie sich anprocessTimeOutMilliseconds
der tatsächlichen Zeit ausgerichtet haben, die der Prozess zum Abschließen benötigt. Mit anderen Worten, zwischen demProcess.Exited
Ereignis und dem Timer tritt eine Rennbedingung auf . Die Möglichkeit, dass dies geschieht, wird erneut durch die asynchrone Natur von vergrößertprocess.Kill()
. Ich habe es einmal beim Testen angetroffen.Der Vollständigkeit halber die
DisposeWith
Erweiterungsmethode.quelle
ExecuteScriptRx
Griffehangs
perfekt. Leider kommt es immer noch zu Hängen, aber ich habe gerade einen kleinen Wrapper über Ihre Leistung hinzugefügtExecuteScriptRx
,Retry
und 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;
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
In Ihrem Code:
Process.WaitForExit(ProcessTimeOutMiliseconds);
Damit warten SieProcess
auf Timeout oder Exit , was immer zuerst stattfindet .OutputWaitHandle.WaitOne(ProcessTimeOutMiliseconds)
underrorWaitHandle.WaitOne(ProcessTimeOutMiliseconds);
Damit warten Sie auf denOutputData
&ErrorData
Stream-Lesevorgang, um den Abschluss zu signalisierenProcess.ExitCode == 0
Ruft den Status des Prozesses ab, wenn er beendet wirdUnterschiedliche Einstellungen und deren Einschränkungen:
ObjectDisposedException()
Process.ExitCode
was zu dem Fehler führtSystem.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
Code aktualisiert
BEARBEITEN:
Nach stundenlangem Herumspielen mit MSBuild konnte ich das Problem endlich auf meinem System reproduzieren
Abschnitt B: Problemwiederherstellung und -lösung
Ich konnte dies auf verschiedene Arten lösen
Spawn MSBuild-Prozess indirekt über CMD
Verwenden Sie MSBuild weiterhin, stellen Sie jedoch sicher, dass nodeReuse auf False gesetzt ist
Selbst wenn die parallele Erstellung nicht aktiviert ist, können Sie verhindern, dass Ihr Prozess hängen bleibt,
WaitForExit
indem Sie die Erstellung über CMD starten. Daher erstellen Sie keine direkte Abhängigkeit vom ErstellungsprozessDer zweite Ansatz wird bevorzugt, da nicht zu viele MSBuild-Knoten herumliegen sollen.
quelle
"-nr:False","-m:3"
scheint das MSBuild-Hang-ish-Verhalten behoben zu haben, wasRx solution
den 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 gebenRx
Ansatz 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 !!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.
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.
quelle
BegingOutputReadline
und führen Sie dannReadToEndAsync
aufStandardError
?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:
Die Codeänderung könnte ungefähr so aussehen:
quelle
process.WaitForExit()
wie in den Kommentaren zu dieser Antwort angegeben .