3

Suppose an object referenced in some PS code is known to have fired 100 events between Register-ObjectEvent and Unregister-Event. Expectation would be for the host code to receive 100 events. However, it sometimes receives fewer than 100, often times a lot less.

I see this consistently happening in Windows 7 with PS v3, v4 and v5.1. The simple test case posted below appears to work under Windows 10, yet the failures in Windows 7 make me wonder if there is maybe something fundamentally wrong with the whole approach.

[ EDIT ]  The same happens in Windows 10 with PS v5.1, only less often than in Windows 7. It took a few thousand runs with $fired = 10000 for it to happen, but it eventually did. [ end EDIT ]

Minimal self-contained code to make it happen (shamelessly adapted from an answer here):

$src = @'
using System;

namespace Utils {
public static class StaticEventTest 
{
    public static event EventHandler Fired;

    public static void RaiseFired()
    {
        if (Fired != null) 
        { 
            Fired(typeof(StaticEventTest), EventArgs.Empty); 
        }
    }
}}
'@

Add-Type -TypeDefinition $src

$fired = 1000
$global:recvd = 0

$srcId = 'Fired'

$id = Register-ObjectEvent ([Utils.StaticEventTest]) Fired `
        -SourceIdentifier $srcId -Action { $global:recvd++ }

for ($i = 1; $i -le $fired; $i++) {
    [Utils.StaticEventTest]::RaiseFired()
}

Unregister-Event -SourceIdentifier $srcId
Receive-Job -Job $id -Wait -AutoRemoveJob

if ($fired -eq $global:recvd) {
    ("total {0} events" -f $fired)
} else {
    ("total {0} events fired - {1} received" -f $fired, $global:recvd)
}

Having the above saved as test.ps1 and run in a loop gives something like this (on Windows 7):

C:\etc>for /l %n in (1, 1, 10) do @powershell.exe  -executionPolicy remoteSigned -file test.ps1
total 1000 events
total 1000 events
total 1000 events
total 1000 events fired - 391 received
total 1000 events
total 1000 events
total 1000 events fired - 59 received
total 1000 events
total 1000 events fired - 199 received
total 1000 events

Any insghts much appreciated.

dxiv
  • 16,984
  • 2
  • 27
  • 49

1 Answers1

0

You are "losing" events because you end your script too early. Your event action defined by Register-ObjectEvent ([Utils.StaticEventTest]) Fired -SourceIdentifier $srcId -Action { $global:recvd++ } will be executed asynchronously to your script. If I repeat what you did, I get even worse results (maybe due to a virtual environment):

C:\Users\user\Desktop>for /l %n in (1, 1, 10) do @powershell.exe  -executionPolicy remoteSigned -file test.ps1
total 1000 events fired - 17 received
total 1000 events fired - 100 received
total 1000 events fired - 202 received
total 1000 events fired - 16 received
total 1000 events fired - 3 received
total 1000 events fired - 120 received
total 1000 events fired - 1 received
total 1000 events fired - 162 received
total 1000 events fired - 1 received
total 1000 events fired - 27 received

But when I add a Start-Sleep -Seconds 5 after your for-loop, I get this result (at least in my environment):

C:\Users\user\Desktop>for /l %n in (1, 1, 10) do @powershell.exe  -executionPolicy remoteSigned -file test.ps1
total 1000 events
total 1000 events
total 1000 events
total 1000 events
total 1000 events
total 1000 events
total 1000 events
total 1000 events
total 1000 events
total 1000 events

So all events you fired are there, no event is lost. But if you want to see the action taken on them right after you fired them, you have to wait until all (asynchronous) actions have been performed.

Of course, Start-Sleep with a fixed time span is not an option here, because you cannot know how long it takes for a system to execute all actions. But it shows the reason, why you think you are losing events.

A possible way to wait for your events would be this line after your for-loop:

while ($global:recvd -lt $fired) {Start-Sleep -Milliseconds 10}

This even works for 100.000 events per iteration (really tried):

C:\Users\user\Desktop>for /l %n in (1, 1, 10) do @powershell.exe  -executionPolicy remoteSigned -file test.ps1
total 100000 events
total 100000 events
total 100000 events
total 100000 events
total 100000 events
total 100000 events
total 100000 events
total 100000 events
total 100000 events
total 100000 events

Not a single event lost. This makes me believe that the firing and listening for events works as expected. I am not able to reproduce any counterproof.

stackprotector
  • 10,498
  • 4
  • 35
  • 64
  • Lost events still happen on my machine even with that (huge) 5 second sleep, only it takes a higher `$fired` count for it to happen e.g. `total 100000 events fired - 1422 received` (which btw is not just a few events that might get cut off at the end, but a whopping 99% of all events). More generally, yes, introducing random delays at different points in the posted code does cause the issue to happen less often, but it still happens eventually. – dxiv Apr 27 '20 at 19:58
  • Your `while` wait-loop might work for the (very simplified) code posted here, but in real life I do not have direct control over the object that fires the events, and there is no `$fired` count available to compare against. The PS code is simply calling methods exposed by the other object, and each method might fire events, without any indication of whether or how many. – dxiv Apr 27 '20 at 19:59
  • @dxiv I agree that most times you won't get any feedback to be able to wait like this. But I am also convinced that there shouldn't be any need for that. Are you trying to solve a certain problem with eventing? Eventing is a method of asynchronous communication and can be considered acting like a multicast UDP service: information is sent by the "fire and forget" principle. The sender does not care if and who is reading it. And he will especially not get any feedback. The sender could of course listen for events, sent by the recipient of his events, that will confirm any received events... – stackprotector Apr 28 '20 at 14:15
  • But that would not be pure eventing anymore, but instead a sort of protocol defined as a layer above eventing using eventing as a transport layer. I get the impression (also because I read your [other question](https://stackoverflow.com/q/61313233/11942268)) that you need something like a unicast TCP service for communication between processes/threads etc. I'm not an expert, but I think eventing will be the wrong way to do it. – stackprotector Apr 28 '20 at 14:15
  • My actual use-case is a multi-layered flow, where I was assessing PS for the outermost layer to replace existing C# code (which works without a hitch, always had). I am familiar with both COM and C# events, which are predictable and reliable in both un/managed worlds. What PS does, however, is neither. I posted here hoping to find out I was missing something basic and obvious, since I don't know PS too well, unfortunately that doesn't seem to be the case. Bottom line for me is that as long as events fired from a single thread get reordered or discarded, that's as good as useless. – dxiv Apr 28 '20 at 23:30