PowerShell事件接收顺序不正确 [英] PowerShell events received out of sequence

查看:74
本文介绍了PowerShell事件接收顺序不正确的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

听起来合理的期望是,应该以触发顺序从一个线程触发同一线程.但是,事实并非如此.这是已知/已记录的行为,是否有任何纠正措施?

It sounds like a reasonable expectation that events fired from one and the same thread should be received in the order in which they were fired. However, that doesn't seem to be the case. Is this a known/documented behavior, and is there any recourse to correct it?

下面是展示此问题的两个现成的代码段,已在Win7和Win10上使用PS v5.1进行了测试.

Below are two ready-to-run code snippets that exhibit the issue, tested with PS v5.1 under both Win7 and Win10.

(a)在单独的作业(即不同的进程)中从线程触发的事件.

(a) Events fired from a thread in a separate job (i.e. a different process).

$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次运行中的批次).

Sample output from a failure case (out of a batch of 100 consecutive runs).

   -?- 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)从单独的运行空间(即不同的线程)触发的事件.

(b) Events fired from a separate runspace (i.e. a different thread).

$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)下发布的样本,不同之处在于少数运行也完全放弃了事件.但是,这很可能与其他问题基于动作的对象事件有时会丢失.

Failure cases resemble the sample one posted under (a) above, except a few runs also had events dropped altogether. This, however, is more likely related to the other question Action-based object events sometimes lost.

total 1000 events fired, 1000 posted, 999 received
484 out-of-sequence events


[ EDIT ] 我专门针对情况(b)进行了一些其他测试,并确认:


[ EDIT ]   I ran some additional tests for case (b) specifically, and confirmed that:

  • 总是在同一托管线程上调用接收操作(其中$global:recvd++)(通过在调用之间保存并比较[System.Threading.Thread]::CurrentThread.ManagedThreadId来确认);

  • the receiving Action (where $global:recvd++) is always called on the same managed thread (this was confirmed by saving and comparing the [System.Threading.Thread]::CurrentThread.ManagedThreadId between calls);

在执行过程中未重新输入接收动作(通过添加全局"嵌套"计数器,在[System.Threading.Interlocked]::Increment/Decrement调用之间包装该动作并检查该计数器是否永不确认)接受01之外的任何其他值.)

the receiving Action is not re-entered during execution (this was confirmed by adding a global "nesting" counter, wrapping the Action between [System.Threading.Interlocked]::Increment/Decrement calls and checking that the counter never takes any values other than 0 and 1).

这些消除了一些可能的比赛条件,但仍然无法解释为什么观察到的行为正在发生或如何纠正,因此原始问题仍然悬而未决.

These eliminate a couple of possible race conditions, but still do not explain why the observed behavior is happening or how to correct it, so the original question remains open.

推荐答案

这是已知/已记录的行为吗?

Is this a known/documented behavior?

正常情况下"事件处理是设计上是异步的.在PowerShell中,如Register-EngineEvent -Action这样的cmdlet就是这种情况.这确实是已知的和预期的行为.您可以在此处

"Normally" event handling is asynchronous by design. And this is the case in PowerShell with cmdlets like Register-EngineEvent -Action. This is indeed known and intended behaviour. You can read more about PowerShell eventing here and here. Both Microsoft sources point out, that this way of event handling is asynchronous:

PowerShell Eventing使您可以响应异步通知 许多对象都支持.

PowerShell Eventing lets you respond to the asynchronous notifications that many objects support.

注意这些cmdlet仅可用于异步.NET事件.它的 无法使用来为同步事件设置事件处理程序 PowerShell事件cmdlet.这是因为所有同步事件 在同一线程上执行,并且cmdlet期望(要求) 事件将在另一个线程上发生.没有第二个线程, PowerShell引擎将仅阻塞主线程,而不会执行任何操作 永远被执行.

NOTE These cmdlets can only be used for asynchronous .NET events. It’s not possible to set up event handlers for synchronous events using the PowerShell eventing cmdlets. This is because synchronous events all execute on the same thread and the cmdlets expect (require) that the events will happen on another thread. Without the second thread, the PowerShell engine will simply block the main thread and nothing will ever get executed.

这基本上就是您正在做的.您可以将事件从后台作业转发到已定义操作的事件订阅者,并在不阻止后台作业的情况下执行该操作.据我所知,没有什么可期待的了.没有要求以任何特殊顺序处理转发的事件.甚至-Forward开关也不能确保其他任何事情,除了传递事件之外:

So that's basically what you are doing. You forward events from your background job to the event subscriber that has an action defined and perform the action without blocking your background job. As far as I can tell, there is nothing more to expect. There is no requirement specified to process the forwarded events in any special order. Even the -Forward switch does not ensure anything more, except passing the events:

指示cmdlet将与此预订有关的事件发送到 本地计算机上的会话.在您使用时,请使用此参数 在远程计算机或远程会话中注册事件.

Indicates that the cmdlet sends events for this subscription to the session on the local computer. Use this parameter when you are registering for events on a remote computer or in a remote session.

很难找到cmdlet内部的任何文档,甚至可能找不到.请记住,Microsoft不会发布过去有关内部afaik的任何文档,而是由MVP来猜测内部发生的事情并撰写有关此内容的书(过分表达).

It is hard and maybe impossible to find any documentation on the internals of the cmdlets. Keep in mind that Microsoft does not publish any documentation about internals afaik from the past, instead it is up to the MVPs to guess what happens inside and write books about it (drastically expressed).

因此,由于无需按特定顺序处理事件,并且PowerShell仅具有执行事件队列上的操作的任务,因此还可以并行执行这些操作以加速事件队列的处理.

So as there is no requirement to process the events in a certain order, and PowerShell just has the task to perfrom actions on an event queue, it is also allowed to perform those actions in parallel to accelerate the processing of the event queue.

在仅具有一个vCPU的VM上测试脚本.有时仍然会出现错误的顺序,但是这种情况很少发生.因此,较少的(实际)并行性,较少的加扰顺序的可能性.当然,您不能阻止由在一个物理内核上执行的不同线程实现的逻辑并行性.因此,仍然存在一些错误".

Test your scripts on a VM with only one vCPU. The wrong order will still occur sometimes, but way more rarely. So less (real) parallelism, less possibilities to scramble the order. Of course, you cannot prevent the logical parallelism, implemented by different threads executed on one physical core. So some "errors" remain.

有任何纠正方法吗?

Is there any recourse to correct it?

我将通常"放在引号中,因为有多种方法可以同步实现它.您将必须实现自己的类型为System.EventHandler的事件处理程序.我建议阅读这篇文章以获得实现示例.

I put "normally" into quotation marks, because there are ways to implement it synchronously. You will have to implement your own event handler of type System.EventHandler. I recommend reading this article to get an example for an implementation.

另一种解决方法是将事件存储在自己的事件队列中,并在收集后对其进行排序(在ISE中运行,但尚未在PS中运行):

Another workaround is to store the events in an own event queue and sort them after collection (runs in ISE, not yet in PS):

$events = 10000
$recvd = 0
$ooseq = 0
$myEventQueue = @()

$job = Register-EngineEvent -SourceIdentifier 'Posted' -Action {$global:myEventQueue += $event}

$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

Write-Host "Wrong event order in unsorted queue:"
$i = 1
foreach ($event in $myEventQueue) {
    if ($i -ne $event.messageData) {
        Write-Host "Event $($event.messageData) received as event $i"
    }
    $i++
}

$myEventQueue = $myEventQueue | Sort-Object -Property EventIdentifier

Write-Host "Wrong event order in sorted queue:"
$i = 1
foreach ($event in $myEventQueue) {
    if ($i -ne $event.messageData) {
        Write-Host "Event $($event.messageData) received as event $i"
    }
    $i++
}


已归档的链接:


Archived links:

  • PowerShell eventing async 1
  • PowerShell eventing async 2
  • PowerShell eventing sync

这篇关于PowerShell事件接收顺序不正确的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆