Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

1.3K
Views
Process.StandardOutput.Readline() se cuelga cuando no hay salida

Nota: Estoy tratando de ejecutar packer.exe como un proceso en segundo plano para solucionar un problema en particular con el generador azure-arm , y necesito ver el resultado. no estoy usando
Start-Process porque no quiero usar un archivo intermediario para consumir la salida.

Tengo el siguiente código configurando packer.exe para que se ejecute en segundo plano, de modo que pueda consumir su salida y actuar sobre un determinado mensaje de registro. Esto es parte de una secuencia de comandos más grande, pero esta es la parte en cuestión que no se comporta correctamente:

 $builderDir = ( Split-Path -Parent $PSCommandPath ) Push-Location $builderDir # Set up the packer command to run asynchronously $pStartProps = @{ FileName = ( Get-Command -CommandType Application packer ).Source Arguments = "build -var-file ""${builderDir}\win-dev.pkrvars.hcl"" -only ""azure-arm.base"" ." UseShellExecute = $false RedirectStandardOutput = $true RedirectStandardError = $false LoadUserProfile = $true } $pStartInfo = New-Object ProcessStartInfo -Property $pStartProps $p = [Process]::Start($pStartInfo) while ( $null -ne ( $output = $p.StandardOutput.Readline() ) -or !$p.HasExited ) { # Do stuff }

Básicamente, la porción ( $output = $p.StandardOutput.Readline() ) de la condición while parece estar suspendida hasta que haya más salida para leer. No estoy seguro de por qué es así, porque StreamReader.Readline() debería devolver la siguiente línea para leer o null si no hay más salida. Tengo un poco de procesamiento en torno a esto con respecto al mensaje de registro que espero recibir, por lo que bloquear al leer STDOUT cuando no hay más resultados para consumir hace que el script sea inútil. Hay otras cosas que está haciendo en primer plano mientras packer.exe continúa ejecutándose.

Puedo confirmar en el depurador que Readline() lee bien las líneas vacías (valor de "" ), esto parece suceder cuando aún no hay más resultados para consumir. Esto puede ser tangencial, pero también hace que el depurador falle.

Cuando ocurre este problema, el depurador VSCode se sienta en esto con
$output = $p.StandardOutput.Readline() resaltado durante unos segundos, luego el depurador se detiene (todo desaparece, no más seguimiento de variables, etc.) hasta Readline() deja de bloquear y la ejecución continúa, momento en el que el depurador parece reinicialice las variables rastreadas, las expresiones observadas, etc. Por lo tanto, no puedo usar el depurador cuando esto ocurre. Incluso el
PowerShell Integrated Console (la que se usa con el depurador) se bloquea y no puedo escribir nada.


Para un contexto completo, el objetivo de este script es dejar que packer.exe haga lo suyo mientras hago un bucle continuo para:

  1. Mostrar más resultados de packer.exe
  2. Comprobar la presencia de un determinado mensaje de registro
  3. Dale a packer.exe un poco de tiempo para intentar hacer lo que necesita por sí solo
  4. Si espera demasiado, ejecuto un script contra el nodo, ya que lo que debería haber hecho packer.exe por sí solo probablemente falló
    • Estoy usando Invoke-AzVMRunCommand para hacer esto, lo que no se puede hacer en el estado packer.exe para el problema en el que estoy trabajando. Debe realizarse fuera de banda de la ejecución de packer.exe .
  5. La compilación continúa después de que se aplica la solución alternativa y simplemente continúo reenviando la salida de packer.exe a la consola hasta que finaliza el proceso.

Pero dado que la secuencia de comandos se bloquea cuando no hay salida, el paso 4 nunca funcionará, ya que tengo que darle tiempo al empaquetador para que intente completar la configuración por sí solo, y es la razón principal por la que estoy hackeando esto en primer lugar.


¿Por qué Readline() está bloqueando aquí? ¿Estoy haciendo algo mal? Este comportamiento ocurre si ejecuto mi script en Windows PowerShell o PowerShell Core.

over 4 years ago · Santiago Trujillo
3 answers
Answer question

0

  • StreamReader.ReadLine() está bloqueando por diseño.

  • Hay una alternativa asíncrona , .ReadLineAsync() , que devuelve una instancia de Task<string> que puede sondear para que se complete, a través de su propiedad .IsCompleted , sin bloquear su subproceso en primer plano (el sondeo es su única opción en PowerShell, dado que tiene ninguna función de lenguaje análoga a await de C#).

Aquí hay un ejemplo simplificado que se enfoca en la lectura asincrónica de una instancia de StreamReader que resulta ser un archivo, al que se agregan nuevas líneas solo periódicamente; use Ctrl-C para abortar.

Espero que el código funcione de la misma manera si lo adapta a su código System.Diagnostics.Process de lectura estándar.

 # Create a sample input file. $n=3 1..$n > tmp.txt # Open the file for reading, and convert it to a System.IO.StreamReader instance. [IO.StreamReader] $reader = [IO.File]::Open("$pwd/tmp.txt", 'Open', 'Read', 'ReadWrite') try { $task = $reader.ReadLineAsync() # Start waiting for the first line. while ($true) { # Loop indefinitely to wait for new lines. if ($task.IsCompleted) { # A new line has been received. $task.Result # Output # Start waiting for the next line. $task.Dispose(); $task = $reader.ReadLineAsync(); } else { # No new line available yet, do other things. Write-Host '.' -NoNewline Start-Sleep 1 } # Append a new line to the sample file every once in a while. if (++$n % 10 -eq 0) { $n >> tmp.txt } } } finally { $reader.Dispose() }
over 4 years ago · Santiago Trujillo Report

0

El StreamReader de la salida estándar está esperando IO: la secuencia de StandardOutput no se puede abrir para leer hasta que obtenga más datos o se cierre el Process . Hay un problema similar con el uso de StreamReader en transmisiones TCP, donde puede disfrutar esperando el tráfico de la red.

La forma habitual de evitarlo es dejar que una tarea diferente la espere a través de lecturas asíncronas como ReadLineAsync() . Esto no ayuda a su ciclo while , porque todavía se sienta y espera la misma cantidad antes de que pueda progresar para leer el StreamReader de la salida.

Si la salida de packer.exe se comporta bien, puede intentar usar la propiedad de salida de un trabajo de PowerShell en lugar de [process] ? Probé con ping que da el mismo comportamiento de bloqueo de espera de IO:

 # Locks up for full ping timeout example: $pStartProps = @{ FileName = 'ping.exe' ; Arguments = '10.bad.ip.0' ... } $p = [System.Diagnostics.Process]::Start($pStartInfo) # Locks when reading $output = $p.StandardOutput.ReadLine() while ( $output -ne $null ) { $output = $p.StandardOutput.ReadLine(); ## read the output # Do stuff } ##### # Does not lock when using Job output instead: $job = Start-Job -ScriptBlock { ping.exe 10.bad.ip.0 } While ($job.State -ne 'Completed') { $job.ChildJobs[0].Output ## read the output # Do stuff }
over 4 years ago · Santiago Trujillo Report

0

Usando StandardOutput.ReadLine() como se indicó anteriormente, configura el flujo de redirección de salida para usar el modo de lectura síncrono. Esto significa que cuando llama a $p.StandardOutput.ReadLine() se comporta como una llamada de función normal.

Lo llamas y esperas el valor de retorno. El valor devuelto es una línea completa o $null si se alcanza el final de la transmisión. Esto significa que a medida que el programa en ejecución llena el búfer de flujo de salida de System.Diagnostics.Process con salida, continúa siendo un buen chico de PowerShell sincrónico y espera a que regrese la llamada a la función. Una vez que System.Diagnostics.Process detecta una nueva línea en la secuencia, la llamada de función $p.StandardOutput.ReadLine() regresa con sus datos, una línea completa completa .

Es por eso que parece que está bloqueando. Tiene que esperar hasta que tenga una línea completa antes de que la función pueda regresar.

El otro valor de retorno, $null , tiene más sentido sabiendo que un objeto StreamReader se usa para muchas cosas diferentes que podrían tener diferentes finales para la secuencia. Si estaba usando un StreamReader para leer un archivo de texto, el final de la secuencia/archivo es cuando golpea el marcador EOF . Si estuviera leyendo una matriz de caracteres, podría ser el terminador de cadena \0 . $null en este caso es un final de flujo universal.

Para un objeto System.Diagnostics.Process , su definición de "el final de una secuencia" es cuando se completa el proceso, no "No tengo salida en este momento".

Para eliminar este bloqueo, debe realizar su lectura de manera asincrónica, también conocida como ReadLineAsync() .

No querer pisar los dedos de los pies de @mklement0, ya que él, correctamente, resuelve su problema usando ReadLineAsync() para hacerlo asíncrono, y sondeando para mantener el script de verificación/validación dentro del mismo alcance. Pero un método alternativo para leer datos de forma asíncrona es usar Process.BeginOutputReadLine() y el evento OutputDataReceived para activar de forma asíncrona un bloque de script cuando tenga más datos.

 # Script to run when you have another line of data $NewOutputSB = { param([object]$sender, [System.Diagnostics.DataReceivedEventArgs]$e) Write-Host " # " $e.Data } $pStartProps = @{ FileName = ( Get-Command -CommandType Application ping.exe ).Source Arguments = "8.8.8.8" UseShellExecute = $false RedirectStandardOutput = $true RedirectStandardError = $false LoadUserProfile = $true } $pStartInfo = New-Object System.Diagnostics.ProcessStartInfo -Property $pStartProps $p = New-Object System.Diagnostics.Process # Register the Event Handler $EventSub = Register-ObjectEvent $p -EventName OutputDataReceived -Action $NewOutputSB $p.StartInfo = $pStartInfo $p.Start() | Out-Null # Begin asynchronous reading on the Output stream $p.BeginOutputReadLine() # Do other work while we wait for it to finish running... while ( !$p.HasExited ) { # Do stuff Start-Sleep -Milliseconds 250 Write-Host "--- Do stuff" } # Cleanup Event Handler Unregister-Event -SubscriptionId $EventSub.Id

Después de ejecutar este código, la salida muestra la secuencia ejecutándose en modo asíncrono:

 PS C:\> # # Pinging 8.8.8.8 with 32 bytes of data: # Reply from 8.8.8.8: bytes=32 time=28ms TTL=119 --- Do stuff --- Do stuff --- Do stuff # Reply from 8.8.8.8: bytes=32 time=28ms TTL=119 --- Do stuff --- Do stuff --- Do stuff --- Do stuff # Reply from 8.8.8.8: bytes=32 time=31ms TTL=119 --- Do stuff --- Do stuff --- Do stuff --- Do stuff # Reply from 8.8.8.8: bytes=32 time=28ms TTL=119 # # Ping statistics for 8.8.8.8: # Packets: Sent = 4, Received = 4, Lost = 0 (0% loss), # Approximate round trip times in milli-seconds: # Minimum = 28ms, Maximum = 31ms, Average = 28ms # --- Do stuff
over 4 years ago · Santiago Trujillo Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!