Звучит как разумное ожидание, что события, запущенные из одного и того же потока, должны приниматься в том порядке, в котором они были запущены. Тем не менее, это не так. Является ли это известным / документированным поведением, и есть ли способы его исправить?
Ниже приведены два готовых к запуску фрагмента кода, которые обнаруживают проблему, протестированных с PS v5.1 под Win7 и Win10.
(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)
}
Случаи сбоев напоминают пример, размещенный в подпункте (а) выше, за исключением в нескольких пробежках также были пропущены события. Это, однако, более вероятно связано с другим вопросом События объекта на основе действий иногда теряются .
total 1000 events fired, 1000 posted, 999 received
484 out-of-sequence events
[
EDIT ] Я выполнил несколько дополнительные тесты для случая (b), в частности, и подтвердили, что:
Получающее действие (где $global:recvd++
) всегда вызывается в одном и том же управляемом потоке (это было подтверждено путем сохранения и сравнения [System.Threading.Thread]::CurrentThread.ManagedThreadId
между вызовами);
Действие получения не вводится повторно во время выполнения (это было подтверждено добавлением глобального счетчика " nesting ", переноса Действие между вызовами [System.Threading.Interlocked]::Increment/Decrement
и проверкой того, что счетчик никогда не принимает никаких значений, кроме 0
и 1
).
Они устраняют пару возможных условий гонки, но все же не объясняйте, почему происходит наблюдаемое поведение или как его исправить, поэтому первоначальный вопрос остается открытым.