Powershell AcceptTcpClient() 不能被 Ctrl-C 中断

Powershell AcceptTcpClient() cannot be interrupted by Ctrl-C

我正在使用 Powershell 编写一个简单的 TCP/IP 服务器。我注意到 Ctrl-C 不能中断 AcceptTcpClient() 调用。 Ctrl-C 在通话后工作正常。找了一圈,至今没有人报告过类似的问题。

下面的简单代码可以重复这个问题。我正在使用 Windows 10,最新的补丁,使用本地 Powershell 终端,而不是 Powershell ISE。

$listener=new-object System.Net.Sockets.TcpListener([system.net.ipaddress]::any, 4444)
$listener.start()
write-host "listener started at port 4444"
$tcpConnection = $listener.AcceptTcpClient()
write-host "accepted a client"

这就是我 运行 它

时发生的情况
ps1> .\test_ctrl_c.ps1
listener started at port 4444
(Ctrl-C doesn't work here)

(从 PowerShell 7.0 开始)Ctrl-C 仅在 PowerShell 代码 正在执行时有效,在执行期间无效.NET 方法.

由于大多数 .NET 方法调用执行得很快,问题通常不会浮出水面。

有关讨论和背景信息,请参阅 this GitHub issue


至于可能的解决方法

  • 最佳方法 - 如果可能 - 是 中显示的方法:

    • 运行 在循环中定期轮询条件,在两次尝试之间休眠,并且仅在满足条件时调用该方法意味着该方法将快速执行而不是无限期地阻塞。
  • 如果这不是一个选项(如果没有这样的条件你可以测试),你可以运行 后台作业,以便它运行在子进程中,可以根据调用者的要求终止;请注意此方法的 局限性 ,但是:

    • 由于需要在隐藏的子进程中 运行 一个新的 PowerShell 实例,后台作业速度缓慢且资源密集。

    • 由于作业的输入和输出的跨进程编组是必要的:

      • 输入和输出不会是活动对象。
      • 复杂对象(原始 .NET 类型实例和一些知名类型实例以外的对象)将是原始对象的仿真;本质上,对象具有 属性 值的静态副本,但没有方法 - 请参阅 了解背景信息。

这里有一个简单的演示:

# Start the long-running, blocking operation in a background job (child process).
$jb = Start-Job -ErrorAction Stop {
  # Simulate a long-running, blocking .NET method call.
  [Threading.Thread]::Sleep(5000)
  'Done.'
}

$completed = $false
try {

  Write-Host -ForegroundColor Yellow "Waiting for background job to finish. Press Ctrl-C to abort."

  # Note: The output collected won't be *live* objects, and with complex
  #       objects will be *emulations* of the original objects that have
  #       static copies of their property values and no methods.
  $output = Receive-Job -Wait -Job $jb

  $completed = $true

}
finally { # This block is called even when Ctrl-C has been pressed.

  if (-not $completed) { Write-Warning 'Aborting due to Ctrl-C.' }

  # Remove the background job.
  #  * If it is still running and we got here due to Ctrl-C, -Force is needed
  #    to forcefully terminate it.
  #  * Otherwise, normal job cleanup is performed.
  Remove-Job -Force $jb

  # If we got here due to Ctrl-C, execution stops here.
}

# Getting here means: Ctrl-C was *not* pressed.

# Show the output received from the job.
Write-Host -ForegroundColor Yellow "Job output received:"
$output
  • 如果你执行上面的脚本并且按Ctrl-C,你会看到:

  • 如果你按Ctrl-C,你会看到:

得到@mklement0的回答后,我放弃了我原来的干净代码。我想出了一个解决方法。现在 Ctrl-C 可以中断我的程序

$listener=new-object System.Net.Sockets.TcpListener([system.net.ipaddress]::any, 4444)
$listener.start()
write-host "listener started at port 4444"
while ($true) {
   if ($listener.Pending()) {
      $tcpConnection = $listener.AcceptTcpClient()
      break;
   }
   start-sleep -Milliseconds 1000
}
write-host "accepted a client"

现在可以使用 Ctrl-C

ps1> .\test_ctrl_c.ps1
listener started at port 4444
(Ctrl-C works here)