Limiting how much runs at onceintermediate
A SemaphoreSlim caps concurrency, but only if it is shared and only if every permit comes back — a Release skipped by an exception drains the pool and the service hangs rather than erroring.
A throttle that stops throttling, then stops working entirely.
One shared semaphore, awaited with the token, released in a finally, and disposed.
Published in full, before you start — every point is one of these and there is nothing else.
A SemaphoreSlim caps concurrency, but only if it is shared and only if every permit comes back — a Release skipped by an exception drains the pool and the service hangs rather than erroring.
A file handle or socket you opened is yours to release — the Dispose pattern is how a caller gets to say "I am finished with this" instead of waiting for a collection that may never come.