public static class SemaphoreSlimExtensions { /// /// Blocks the current thread until it can enter the . Returns an to be used in using. /// /// A to lock. /// An that will release the when disposed. public static IDisposable Lock(this SemaphoreSlim semaphore) { semaphore.Wait(); return new SemaphoreReleaser(semaphore); } /// /// Asynchronously waits to enter the , while observing a /// . Returned task resolves an to be used in using. /// /// /// A task that will complete when the semaphore has been entered. /// Task contains An that will release the when disposed. /// /// A to lock. /// /// The token to observe. /// public static async Task LockAsync(this SemaphoreSlim semaphore, CancellationToken cancellationToken = default) { var task = semaphore.WaitAsync(cancellationToken); await task.ConfigureAwait(false); return task.Status == TaskStatus.RanToCompletion ? new SemaphoreReleaser(semaphore) : new DisposableNoOp(); } private sealed class SemaphoreReleaser(SemaphoreSlim semaphore) : IDisposable { private bool released; public void Dispose() { if (released) return; semaphore.Release(); this.released = true; } } private sealed class DisposableNoOp : IDisposable { public void Dispose() { } } }