Skip to content

Instantly share code, notes, and snippets.

@chrisfcarroll
Created May 27, 2026 10:28
Show Gist options
  • Select an option

  • Save chrisfcarroll/dc50b7ab6eac2cd41b6315eb23f23a62 to your computer and use it in GitHub Desktop.

Select an option

Save chrisfcarroll/dc50b7ab6eac2cd41b6315eb23f23a62 to your computer and use it in GitHub Desktop.
A SemaphoreSlim(1,1) wrapper for use as an async lock in a using statement
/// <summary>
/// A <see cref="SemaphoreSlim"/> wrapper that can be used in a using statement.
/// The wrapped semaphore has initialCount=1, maxCount=1.
/// <code>using(await theUsableSemaphoreSlim.WaitAsync())
/// {
/// ... access resources ...
/// }
/// </code>
/// <p>Calling <see cref="Dispose"/> on a <see cref="UsableSemaphoreSlim"/>
/// does not dispose the Semaphore, it releases the lock.</p>
/// <p>To dispose the underlying lock, call <see cref="DisposeUnderlyingLock"/>.</p>
/// </summary>
public sealed class UsableSemaphoreSlim : IDisposable
{
readonly SemaphoreSlim inner = new(1,1);
/// <summary>
/// Asynchronously waits to enter the <see cref="SemaphoreSlim"/>.
/// </summary>
/// <returns>A task that will complete when the semaphore has been entered.</returns>
/// <exception cref="ObjectDisposedException">
/// The underlying Semaphore has been disposed.
/// </exception>
public async Task<IDisposable> WaitAsync()
{
await inner.WaitAsync();
return this;
}
/// <summary>
/// Blocks the current thread until it can enter the <see cref="SemaphoreSlim"/>.
/// </summary>
/// <exception cref="ObjectDisposedException">
/// The underlying Semaphore has been disposed.
/// </exception>
public IDisposable Wait()
{
inner.Wait();
return this;
}
public WaitHandle Available => inner.AvailableWaitHandle;
/// <summary>
/// Calls <see cref="SemaphoreSlim.Release()"/> on the underlying <see cref="SemaphoreSlim"/>.
/// Calling this method does not dispose the underlying <see cref="SemaphoreSlim"/>.
/// To do that, call <see cref="DisposeUnderlyingLock"/>.
/// </summary>
/// <exception cref="ObjectDisposedException">
/// The underlying Semaphore has been disposed.
/// </exception>
/// <exception cref="SemaphoreFullException">
/// The underlying Semaphore has already reached it's maximum size, presumably
/// because of more calls to <see cref="Dispose"/> than to <see cref="WaitAsync"/>
/// </exception>
public void Dispose() => inner.Release();
/// <summary>
/// Call this to dispose the underlying lock.
/// </summary>
public void DisposeUnderlyingLock() => inner.Dispose();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment