This isn’t an easy problem to solve. But it’s solvable.
A threading trigger is a handy thing. But I want you to notice line 15. For some reason every discussion on this topic assumes the execution of the async event cannot fire faster than the time necessary to move from line 19 to line 20. Now, let’s speak frankly, the likelihood is almost zero. However, it is not zero. Adding line 15 solves the problem without any cost.
ManualResetEvent allows threads to communicate with each other by signaling. Typically, this communication concerns a task which one thread must complete before other threads can proceed.
When a thread begins an activity that must complete before other threads proceed, it calls Reset to put ManualResetEvent in the non-signaled state. This thread can be thought of as controlling the ManualResetEvent. Threads that call WaitOne on the ManualResetEvent will block, awaiting the signal. When the controlling thread completes the activity, it calls Set to signal that the waiting threads can proceed. All waiting threads are released.
Once it has been signaled, ManualResetEvent remains signaled until it is manually reset. That is, calls to WaitOne return immediately.
You can control the initial state of a ManualResetEvent by passing a Boolean value to the constructor, true if the initial state is signaled and false otherwise.
Get the code here.