Dot-Net

沒有輸出時 Process.StandardOutput.Readline() 掛起

  • October 22, 2021

**注意:**我正在嘗試packer.exe作為後台程序執行以解決azure-arm建構器的特定問題,我需要觀察輸出。我沒有使用
Start-Process,因為我不想使用中間文件來消耗輸出。

我將以下程式碼設置packer.exe為在後台執行,這樣我就可以使用它的輸出並對某個日誌消息採取行動。這是一個更大的腳本的一部分,但這是有問題的一點,它的行為不正確:

 $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
 }

基本上,條件的( $output = $p.StandardOutput.Readline() )一部分while似乎一直掛起,直到有更多的輸出要讀取。我不確定為什麼會這樣,因為StreamReader.Readline() 應該返回要讀取的下一行,或者null是否沒有更多輸出。關於我期望獲得的日誌消息,我對此進行了相當多的處理,因此當沒有進一步的輸出消耗時讀取 STDOUT 時阻塞會使腳本無用。packer.exe在繼續執行的同時,它還在前台執行其他操作。

我能夠在Readline()確實讀取空行(的值"")的調試器中確認,這似乎發生在沒有進一步的輸出消耗時。這可能是相切的,但這也會導致調試器出錯。

發生此問題時,VSCode 調試器會在此位置
$output = $p.StandardOutput.Readline()突出顯示幾秒鐘,然後調試器停止(一切都消失,不再跟踪變數等),直到Readline()停止阻塞並繼續執行,此時調試器似乎重新初始化跟踪的變數,監視的表達式等。所以當這種情況發生時我根本無法使用調試器。甚至
PowerShell Integrated Console(與調試器一起使用的那個)也掛起,我無法輸入任何內容。


對於完整的上下文,這個腳本的目標是讓packer.exe我在不斷循環到:

  1. 顯示更多輸出packer.exe
  2. 檢查是否存在某個日誌消息
  3. packer.exe一點時間嘗試自己做它需要做的事情
  4. 如果它等待的時間太長,我會針對節點執行一個腳本,因為packer.exe應該自己完成的操作可能會失敗
  • 我正在這樣做,對於我正在解決的問題Invoke-AzVMRunCommand,這在該州無法完成。packer.exe它必須在packer.exe執行本身的帶外執行。
  1. 應用解決方法後繼續建構,我只是繼續將輸出轉發packer.exe到控制台,直到程序退出

但是由於腳本在沒有輸出時掛起,所以第 4 步將永遠無法工作,因為我必須給加殼程序時間來嘗試自己完成配置,這也是我首先將它一起破解的全部原因。


怎麼Readline()堵在這裡?我做錯了什麼嗎?無論我是在 Windows PowerShell 還是 PowerShell Core 中執行我的腳本,都會出現此行為。

  • StreamReader.ReadLine()被設計阻止。
  • 有一個非同步替代方法,.ReadLineAsync()它返回一個Task<string>實例,您可以通過其屬性輪詢完成,.IsCompleted而不會阻塞您的前台執行緒(輪詢是 PowerShell 中的唯一選項,因為它沒有類似於 C# 的語言功能await)。

這是一個簡化的範例,側重於從StreamReader恰好是文件的實例中非同步讀取,新行僅定期添加到該文件中;用於Ctrl-C中止。

如果您將其調整為您的標準輸出讀取程式碼,我希望程式碼能夠正常工作System.Diagnostics.Process

# 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()
}

引用自:https://stackoverflow.com/questions/69652895