【发布时间】:2020-05-04 07:19:20
【问题描述】:
从同一个线程触发的事件应该按照触发顺序被接收,这听起来像是一个合理的期望。然而,情况似乎并非如此。这是一种已知/记录在案的行为,是否有任何补救措施来纠正它?
以下是两个可运行的代码 sn-ps,它们在 Win7 和 Win10 下使用 PS v5.1 进行了测试。
(a) 从单独作业(即不同进程)中的线程触发的事件。
$events = 1000
$recvd = 0
$ooseq = 0
$job = Register-EngineEvent -SourceIdentifier 'Posted' -Action {
$global:recvd++
if($global:recvd -ne $event.messageData) {
$global:ooseq++
("-?- event #{0} received as #{1}" -f $event.messageData, $global:recvd)
} }
$run = Start-Job -ScriptBlock {
Register-EngineEvent -SourceIdentifier 'Posted' -Forward
for($n = 1; $n -le $using:events; $n++) {
[void] (New-Event -SourceIdentifier 'Posted' -MessageData $n)
} }
Receive-Job -Job $run -Wait -AutoRemoveJob
Unregister-Event -SourceIdentifier 'Posted'
Receive-Job -Job $job -Wait -AutoRemoveJob
if($events -eq $script:recvd) {
("total {0} events" -f $events)
} else {
("total {0} events events, {1} received" -f $events, $recvd)
}
if($ooseq -ne 0) {
("{0} out-of-sequence events" -f $ooseq)
}
来自失败案例的样本输出(在一批 100 次连续运行中)。
-?- event #545 received as #543
-?- event #543 received as #544
-?- event #546 received as #545
-?- event #544 received as #546
total 1000 events
4 out-of-sequence events
(b) 从单独的运行空间(即不同的线程)触发的事件。
$recvd = 0
$ooseq = 0
$job = Register-EngineEvent -SourceIdentifier 'Posted' -Action {
$global:recvd++
if($recvd -ne $event.messageData) {
$global:ooseq++
("-?- event #{0} received as #{1}" -f $event.messageData, $recvd)
}}
$sync = [hashTable]::Synchronized(@{})
$sync.Host = $host
$sync.events = 1000
$sync.posted = 0
$rs = [runspaceFactory]::CreateRunspace()
$rs.ApartmentState = "STA"
$rs.ThreadOptions = "ReUseThread"
$rs.Open()
$rs.SessionStateProxy.SetVariable("sync",$sync)
$ps = [powerShell]::Create().AddScript({
for($n = 1; $n -le $sync.events; $n++) {
$sync.Host.Runspace.Events.GenerateEvent('Posted', $null, $null, $n)
$sync.posted++
}})
$ps.runspace = $rs
$thd = $ps.BeginInvoke()
$ret = $ps.EndInvoke($thd)
$ps.Dispose()
Unregister-Event -SourceIdentifier 'Posted'
Receive-Job -Job $job -Wait -AutoRemoveJob
if($sync.events -eq $recvd) {
("total {0} events" -f $sync.events)
} else {
("total {0} events fired, {1} posted, {2} received" -f $sync.events, $sync.posted, $recvd)
}
if($ooseq -ne 0) {
("{0} out-of-sequence events" -f $ooseq)
}
失败案例类似于上面 (a) 下发布的示例,除了一些运行也完全丢弃了事件。然而,这更有可能与另一个问题Action-based object events sometimes lost有关。
total 1000 events fired, 1000 posted, 999 received
484 out-of-sequence events
[ EDIT ] 我专门针对案例 (b) 进行了一些额外的测试,并确认:
接收动作(
$global:recvd++)总是在同一个托管线程上调用(这通过保存和比较调用之间的[System.Threading.Thread]::CurrentThread.ManagedThreadId得到确认);在执行期间未重新输入接收动作(这已通过添加全局“嵌套”计数器来确认,将动作包装在
[System.Threading.Interlocked]::Increment/Decrement调用之间并检查计数器永远不会取除0和1以外的任何值。
这些消除了几个可能的竞争条件,但仍然没有解释观察到的行为发生的原因或如何纠正它,因此原始问题仍然悬而未决。
【问题讨论】:
标签: powershell events