¿Cuál puede ser la razón de que mi proceso se cuelgue mientras espero la salida?
Este código tiene que iniciar el script de PowerShell que en su interior realiza muchas acciones, por ejemplo, comenzar a recompilar el código a través de MSBuild, pero probablemente el problema es que genera demasiada salida y este código se atasca mientras espera salir incluso después de que el script de Power Shell se ejecute correctamente
es un poco "extraño" porque a veces este código funciona bien y otras simplemente se atasca.
El código se cuelga en:
process.WaitForExit (ProcessTimeOutMiliseconds);
El script de Powershell se ejecuta en 1-2 segundos, mientras que el tiempo de espera es de 19 segundos.
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);
}
}
}
Guión:
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;
}
dónde
$args[0] = "C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\MSBuild\Current\Bin\MSBuild.exe"
Editar
A la solución de @ ingen agregué un pequeño contenedor que reintenta ejecutar MS Build colgado
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;
}
¿Dónde InternalExecuteScript
está el código de ingen
Rx
enfoque funcionó (ya que no se agotó el tiempo) incluso con el proceso de MSBuild perdido que condujo a una espera indefinida? interesado en saber cómo se manejó esoRespuestas:
Comencemos con un resumen de la respuesta aceptada en una publicación relacionada.
Incluso la respuesta aceptada, sin embargo, lucha con el orden de ejecución en ciertos casos.
Es en este tipo de situaciones, donde desea organizar varios eventos, que Rx realmente brilla.
Tenga en cuenta que la implementación .NET de Rx está disponible como el paquete System.Reactive NuGet.
Vamos a sumergirnos para ver cómo Rx facilita el trabajo con eventos.
FromEventPattern
nos permite asignar distintas ocurrencias de un evento a una secuencia unificada (también conocida como observable). Esto nos permite manejar los eventos en una tubería (con semántica similar a LINQ). LaSubscribe
sobrecarga utilizada aquí se proporciona con anAction<EventPattern<...>>
y anAction<Exception>
. Cada vez que se produce el evento observado, susender
yargs
será envueltoEventPattern
y empujado a través delAction<EventPattern<...>>
. Cuando se genera una excepción en la canalización,Action<Exception>
se utiliza.Uno de los inconvenientes del
Event
patrón, claramente ilustrado en este caso de uso (y por todas las soluciones en la publicación referenciada), es que no es aparente cuándo / dónde cancelar la suscripción de los controladores de eventos.Con Rx volvemos y
IDisposable
cuando hacemos una suscripción. Cuando lo desechamos, efectivamente finalizamos la suscripción. Con la adición delDisposeWith
método de extensión (prestado de RxUI ), podemos agregar múltiplesIDisposable
s a unCompositeDisposable
(nombradodisposables
en los ejemplos de código). Cuando hayamos terminado, podemos finalizar todas las suscripciones con una sola llamadadisposables.Dispose()
.Sin duda, no hay nada que podamos hacer con Rx, que no podríamos hacer con Vanilla .NET. El código resultante es mucho más fácil de razonar, una vez que se haya adaptado a la forma funcional de pensar.
Ya discutimos la primera parte, donde asignamos nuestros eventos a observables, para que podamos saltar directamente a la parte carnosa. Aquí asignamos nuestro observable a la
processExited
variable, porque queremos usarlo más de una vez.Primero, cuando lo activamos, llamando
Subscribe
. Y más tarde cuando queremos 'esperar' su primer valor.Uno de los problemas con OP es que supone
process.WaitForExit(processTimeOutMiliseconds)
que terminará el proceso cuando se agote el tiempo de espera. De MSDN :En cambio, cuando se agota el tiempo de espera, simplemente devuelve el control al subproceso actual (es decir, deja de bloquear). Debe forzar manualmente la terminación cuando el proceso agota el tiempo de espera. Para saber cuándo ha transcurrido el tiempo de espera, podemos asignar el
Process.Exited
evento a unprocessExited
observable para su procesamiento. De esta manera podemos preparar la entrada para elDo
operador.El código se explica por sí mismo. Si
exitedSuccessfully
el proceso habrá terminado con gracia. Si noexitedSuccessfully
, la terminación deberá ser forzada. Tenga en cuenta queprocess.Kill()
se ejecuta de forma asíncrona, comentarios de referencia . Sin embargo, llamarprocess.WaitForExit()
inmediatamente después abrirá la posibilidad de puntos muertos nuevamente. Por lo tanto, incluso en el caso de terminación forzada, es mejor dejar que todos los desechables se limpien cuandousing
finalice el alcance, ya que la salida puede considerarse interrumpida / dañada de todos modos.La
try catch
construcción está reservada para el caso excepcional (sin juego de palabras) en el que se haya alineadoprocessTimeOutMilliseconds
con el tiempo real necesario para completar el proceso. En otras palabras, se produce una condición de carrera entre elProcess.Exited
evento y el temporizador. La posibilidad de que esto suceda se ve nuevamente aumentada por la naturaleza asincrónica deprocess.Kill()
. Lo encontré una vez durante las pruebas.Para completar, el
DisposeWith
método de extensión.fuente
ExecuteScriptRx
manijashangs
perfectamente. Desafortunadamente aún se producen bloqueos, pero acabo de agregar un pequeño envoltorio sobre tuExecuteScriptRx
que funcionaRetry
y luego se ejecuta bien. La razón de los bloqueos de MSBUILD puede ser la respuesta de @Clint. PD: Ese código me hizo sentir estúpido <lol> Esa es la primera vez que veoSystem.Reactive.Linq;
Para beneficio de los lectores, voy a dividir esto en 2 secciones.
Sección A: problema y cómo manejar escenarios similares
Sección B: recreación del problema y solución
Sección A: Problema
En su código:
Process.WaitForExit(ProcessTimeOutMiliseconds);
Con esto, está esperando el TiempoProcess
de espera o la Salida , lo que ocurra primero .OutputWaitHandle.WaitOne(ProcessTimeOutMiliseconds)
yerrorWaitHandle.WaitOne(ProcessTimeOutMiliseconds);
con esto le está esperandoOutputData
yErrorData
flujo de operación de lectura para señalar su completaProcess.ExitCode == 0
Obtiene el estado del proceso cuando salióDiferentes configuraciones y sus advertencias:
ObjectDisposedException()
Process.ExitCode
que produce el errorSystem.InvalidOperationException: Process must exit before requested information can be determined
.He probado este escenario más de una docena de veces y funciona bien, se han utilizado las siguientes configuraciones durante las pruebas
Código actualizado
EDITAR:
Después de horas de jugar con MSBuild, finalmente pude reproducir el problema en mi sistema
Sección B: Recreación de problemas y solución
Pude resolver esto de dos maneras
Genera el proceso MSBuild indirectamente a través de CMD
Continúe usando MSBuild pero asegúrese de establecer el nodo Reutilizar en Falso
Incluso si la compilación paralela no está habilitada, aún puede evitar que su proceso se bloquee
WaitForExit
iniciando la compilación a través de CMD y, por lo tanto, no cree una dependencia directa en el proceso de compilaciónSe prefiere el segundo enfoque ya que no desea que haya demasiados nodos MSBuild por ahí.
fuente
"-nr:False","-m:3"
parece haber solucionado el comportamiento de MSBuild hang-ish, lo que haRx solution
hecho que todo el proceso sea algo confiable (el tiempo se mostrará). Desearía poder aceptar ambas respuestas o dar dos recompensasRx
enfoque en la otra solución podía resolver el problema sin aplicar-nr:False" ,"-m:3"
. Según tengo entendido, maneja la espera indefinida de puntos muertos y otras cosas que había cubierto en la sección 1. Y la causa raíz en la Sección 2 es la que creo que es la causa raíz del problema que enfrentaron;) Puedo estar equivocado, por eso Pregunté, solo el tiempo lo dirá ...El problema es que si redirige StandardOutput y / o StandardError, el búfer interno puede llenarse.
Para resolver los problemas mencionados anteriormente, puede ejecutar el proceso en subprocesos separados. No uso WaitForExit, utilizo el evento de proceso salido que devolverá el código de salida del proceso de forma asincrónica asegurando que se haya completado.
El código anterior se prueba en batalla llamando a FFMPEG.exe con argumentos de línea de comando. Estaba convirtiendo archivos mp4 en archivos mp3 y haciendo más de 1000 videos a la vez sin fallar. Desafortunadamente, no tengo experiencia directa en el shell de energía, pero espero que esto ayude.
fuente
BegingOutputReadline
y después de realizarReadToEndAsync
elStandardError
?No estoy seguro de si este es su problema, pero al mirar MSDN parece haber algo extraño con el WaitForExit sobrecargado cuando redirige la salida de forma asincrónica. El artículo de MSDN recomienda llamar a WaitForExit que no toma argumentos después de llamar al método sobrecargado.
Página de documentos ubicada aquí. Texto relevante:
La modificación del código podría verse así:
fuente
process.WaitForExit()
lo indicado por los comentarios a esta respuesta .