在事件驱动脚本中,我正在寻找一种从后台作业或运行空间为主线程触发自定义事件的方法。 后台处理非常耗时并且不能异步,这就是它被委托给后台作业或运行空间的原因。 脚本的主要部分是事件驱动的,以避免定期轮询和不必要的资源消耗。
脚本的总体结构如下。
$DataCollectionScriptBlock = {
#
... Initialization stuff
#
while ( $stopRequested -ne 'yes' ) {
#
... collect / filter / consolidate data
#
fire event 'datacollection' for main Thread with necessary data as event argument
#
}
#
... termination stuff
#
}
$DataConsumerScriptBlock = {
# retrieve necessary data
$data = $Event.SourceEventArgs
#
... perform necessary stuff
#
}
#
# main thread operations
#
...
# start background job (or runspace)
Start-Job -ScriptBlock $DataCollectionScriptBlock -InputObject <something> ...
# subscibe to datacollection event
Register-EngineEvent -SourceIdentifier 'datacollection' -Action $DataConsumerScriptBlock
#
...
#
Wait-Event
为了在 $DataCollectionScriptBlock 中生成事件,我尝试了 New-Event cmdlet,但它在作业/运行空间线程中本地生成事件。 该事件不能在主线程中使用。
我也尝试过使用BackgroundWorker类但没有成功。
我还阅读了该论坛上的几篇帖子,其中有大量 C# 示例,但没有找到在 Powershell 中实现的简单解决方案。
Start-Job
通过隐藏子进程使用跨进程并行性;因此,不支持使用events(这是一个in-process功能)。
但是,通过基于线程的并行性,其中多个运行空间并行运行在同一进程中,解决方案是可能的,基于:
识别应接收事件的目标运行空间,在独立 PowerShell 会话中始终是 Get-Runspace
(
(Get-Runspace)[0]
) 报告的first运行空间
GenerateEvent()
实例的 System.Management.Automation.Runspaces.Runspace
属性上调用 .Events
方法。
为简单起见,以下独立示例使用PowerShell(核心)7+的
-Parallel
cmdlet的
ForEach-Object
功能,该功能可创建并行运行空间并同步执行它们。但是,我希望代码能够与 Start-ThreadJob
以及通过 PowerShell SDK 手动创建的运行空间同样工作。
#
# main thread operations
#
# Subscribe to a custom datacollection event
$job = Register-EngineEvent -SourceIdentifier datacollection -Action {
# Sample event processing that prints directly to the display.
@"
Event received:
Sender: $($Event.Sender)
Event args: $($Event.SourceArgs | Out-String)
"@ | Out-Host
}
# Use ForEach-Object -Parallel to create two parallel runspaces,
# and make them each trigger an event for the main runspace.
# I expect this to work equally with Start-ThreadJob and manual runspace creation.
1..2 | ForEach-Object -Parallel {
# Get-Runspace | out-string
# [runspace]::DefaultRunspace | out-string
# '---'
# $null = New-Event -SourceIdentifier datacollection -EventArguments @{ foo = 'bar' } -sender $_
(Get-Runspace)[0].Events.GenerateEvent('datacollection', $_, @{ foo = 'bar' }, $null)
}
# Keep the script alive until Ctrl-C is pressed
# (Due to use of an -Action script block with Register-EngineEvent,
# Wait-Process doesn't output any events; it just waits definitely, during
# which time the -Action script block can run).
try {
$null = Wait-Event
} finally {
# Clean up.
$job | Remove-Job -Force
}