Single-waiter variants
Every primitive that a task can wait on comes in two variants, chosen by SuspensionKind:
| Plain name, e.g. Semaphore | _s name, e.g. Semaphore_s |
|
|---|---|---|
| Waiting tasks at a time | any number (up to 65535) | at most one |
| Cost | a lock-free wait list | a single slot — cheaper |
| Timeouts | no | yes, Wait(Duration) |
| Waking order | LIFO, no fairness | — |
The pairs: AutoResetEvent / AutoResetEvent_s, ManualResetEvent / ManualResetEvent_s, Semaphore / Semaphore_s, CriticalSection / CriticalSection_s, TaskGroup / TaskGroup_s, WaitableTask / WaitableTask_s, and the waitable I/O requests. The scope guards SemaphoreLock and CriticalSectionLock, and their spinning versions, take either variant.
Choosing
Most waits in a task graph have exactly one waiter: the next stage waits for its input, a
coordinator waits for its group, a task waits for its own I/O. Use the _s variant there — it is
faster and gives you timeouts. Use the plain variant only where several tasks really wait on the
same object at the same time.
Important
An _s primitive with two waiting tasks at the same time is invalid use. Signaling is not
limited: any number of tasks and threads may Set or Release an _s primitive.
Destruction
The _s events may be destroyed right after their single waiter has returned from Wait. For the
plain variants that is not enough: the signaling side may still be resuming other waiters. Each
reference page says exactly when its object may go away.