保持 PowerShell 进程处于活动状态而不中断异步事件处理程序

5 powershell

概括

我想知道保持 PowerShell 脚本运行而不使主线程进入睡眠状态的最佳方法。使用Start-Sleep保持脚本活动状态会产生阻塞同一线程上运行的异步事件处理程序的副作用。

示例代码

考虑以下脚本,它演示了这种阻塞的实际情况。

$Timer = [System.Timers.Timer]::new(500)
Register-ObjectEvent -InputObject $Timer -EventName Elapsed -Action { Write-Host -ForegroundColor Blue hi }
$Timer.Start()
Start-Sleep -Seconds 5
Run Code Online (Sandbox Code Playgroud)

运行此脚本时,您将看到在命令完成Timer之前,它的处理程序被阻止执行。Start-Sleep但是,您还会注意到事件在后台排队,并且一旦主线程被释放,所有事件都会快速连续触发。

Start-Sleep此外,一旦命令运行完毕,脚本就会退出。

在此输入图像描述

问题:如何保持 PowerShell 进程/脚本运行而不阻塞事件处理程序?

小智 4

大约一年前,我在 GitHub 上的 PowerShell Polaris 存储库上提出了类似的问题。该特定模块的答案是使用 PowerShellWait-Event命令。

使用Wait-Event随机命令-SourceIdentifier将阻止主线程的进一步执行,而不会阻止事件处理程序的执行。

$Timer = [System.Timers.Timer]::new(500)
Register-ObjectEvent -InputObject $Timer -EventName Elapsed -Action { Write-Host -ForegroundColor Blue hi }
$Timer.Start()
Wait-Event -SourceIdentifier SomeEventIdThatWillNeverExist
Run Code Online (Sandbox Code Playgroud)

有条件终止

如果脚本已“完成”(无论这对作者意味着什么),并且注册的事件处理程序希望终止脚本,他们可以这样做。

-Action在本例中,定时器上的事件处理程序可以选择使用内置命令来触发一个事件,并将SomeEventIdThatWillNeverExist用作其参数值。否则,外部人员可以在适当的时候强制终止该进程。-SourceIdentifierNew-Event

$Timer = [System.Timers.Timer]::new(500)
Register-ObjectEvent -InputObject $Timer -EventName Elapsed -Action {
  Write-Host -ForegroundColor Blue hi
  if ((Get-Random -Maximum 100) -gt 95) {
    New-Event -SourceIdentifier SomeEventIdThatWillNeverExist
  }
}
$Timer.Start()
Wait-Event -SourceIdentifier SomeEventIdThatWillNeverExist
Run Code Online (Sandbox Code Playgroud)

在交互式 PowerShell 会话中,计时器将在脚本完成执行后继续运行。您需要使用单独的 PowerShell 进程(即pwsh -File timer.ps1)来调用脚本。

在此输入图像描述

目前,这是我所知道的最好答案。然而,如果有更好的方法来实现这一点,我愿意接受新的答案。

  • 该解决方案有点晦涩难懂,但如果目的是在处理与事件关联的“-Action”脚本块时简单地“阻止”主线程,那么这可能是最好的解决方案。请注意,在这种情况下,即使是“Wait-Event”本身也可以,因为“-Action”脚本块的输出是通过“*-Job”cmdlet 报告的,而不是通过“*-Event”cmdlet 报告的,因此不应有任何事件永远不会到达(除非您还使用了 _other_ events _without_ `-Action`,但是您必须在主线程中处理它们才能根据它们采取操作,从而防止简单地阻塞主线程)。 (2认同)
  • 同意,您的后一种情况可能已经成熟,可以进行关注点分离。在这个特定示例中,处理定时器之外的事件队列可能需要单独的脚本(应用程序)。 (2认同)
  • 重新终止:如果您将整个调用包装在“try”语句中,并在其“finally”子句中执行以下内容,则事件作业将被删除,并且计时器将被释放(从而停止),即使您通过 Ctrl- 终止C:“找工作| 删除作业-强制;$Timer.Dispose()` (或者捕获变量中 `Register-ObjectEvent` 的输出并将其通过管道传输到 `Remove-Job ...`)。 (2认同)