Why PulseEvent () is unreliable and what to do with it
The auto-reset event is king!
PulseEvent appeared only in Windows NT 4.0. It did not exist in the original Windows NT 3.1. In contrast, robust features like CreateEvent, SetEvent, and WaitForMultipleObjects existed from the very beginning of Windows NT, so consider using them.
The CreateEvent function has an argument bManualReset. If this parameter is TRUE, the function creates a reset event object that requires the use of the ResetEvent function to set the status of the event without signaling. This is not what you need. If this parameter is FALSE, the function creates an auto-reset event object, and the system automatically resets the event state to a non-signal after the release of one waiting thread.
These auto-reset events are very reliable and easy to use.
If you expect an auto-reset event object with WaitForMultipleObjects or WaitForSingleObject, it reliably resets the event when you exit these wait functions.
So, create events as follows:
EventHandle := CreateEvent(nil, FALSE, FALSE, nil);
Wait for an event from one thread and make a SetEvent from another thread. It is very simple and very reliable.
Never call ResetEvent (as it will automatically reset) or PulseEvent (since it is not reliable and deprecated). Even Microsoft has recognized that PulseEvent should not be used. See https://msdn.microsoft.com/en-us/library/windows/desktop/ms684914(v=vs.85).aspx
This function is unreliable and should not be used, because only those threads that are in the waiting state at the time of PulseEvent call will be notified. If they are in any other state, they will not be notified, and you can never know exactly what the state of the stream is. Waiting for a thread in a synchronization object can be temporarily removed from the idle state by asynchronously invoking kernel mode procedures, and then returning to the idle state after the APC completes. If a PulseEvent call occurs during the time that the thread was removed from the idle state, the thread will not be released, because the PulseEvent only releases threads that are waiting at the time it is called.
You can learn more about asynchronous kernel mode calls in the following links:
We have never used PulseEvent in our applications. As for auto-reset events, we use them with Windows NT 3.51, and they work very well.
What to do if several threads are waiting for one object
Unfortunately, your case is a little more complicated. You have several threads waiting for an event, and you need to make sure that all threads have actually received a notification. There is no other reliable way than creating your own event for each thread.
You wrote theat "the only solution I see is for each thread to register its use of this event object with this thread of the object owner." It is right.
You also wrote that "the owner thread can determine when the signal state of the event object is safely reset" - this is impractical and unsafe. It is best to use auto-reset events, so they will automatically reset.
So, you will need to have as many events as there are threads. In addition, you will need to save the list of registered streams. Therefore, to notify all threads, you will need to do a SetEvent in a loop for all event descriptors. This is a very fast, reliable and cheap way. Events are much cheaper than streams. Thus, the number of threads is a problem, not the number of events. There are practically no restrictions on kernel objects - the processor limit for kernel descriptors is 2 ^ 24.