Class AsyncSemaphore
- Namespace
- CryptoHives.Foundation.Threading.Async.Pooled
- Assembly
- CryptoHives.Foundation.Threading.dll
An async version of SemaphoreSlim which uses a pooled approach to implement waiters for ValueTask to reduce memory allocations.
public sealed class AsyncSemaphore
- Inheritance
-
AsyncSemaphore
- Inherited Members
Remarks
This implementation uses ValueTask for waiters and provides allocation-free async signaling by reusing pooled IValueTaskSource<TResult> instances to avoid allocations of TaskCompletionSource<TResult> and Task.
Optional timeout and cancellation token parameters on WaitAsync(TimeSpan, CancellationToken).
Important Usage Note: Awaiting on ValueTask has its own caveats, as it is a struct that can only be awaited or converted with AsTask() ONE single time. Additional attempts to await after the first await or additional conversions to AsTask() will throw an InvalidOperationException.
Continuation Scheduling: The RunContinuationAsynchronously property controls how continuations are executed when a permit is released. When set to true (default), continuations are forced to queue to the thread pool, preventing the releasing thread from being blocked by continuation execution.
Allocation Behavior: Immediate acquisitions are completely allocation-free using atomic
operations. When the semaphore is contended, waiting without a timeout is allocation-free on .NET 6.0+
(using UnsafeRegister for cancellation), while older frameworks may allocate for cancellation
registration. Specifying a finite timeout allocates a timer that is automatically disposed when the
operation completes. Exception and task allocations occur only if a timeout actually elapses or
cancellation is triggered; successful acquisitions are otherwise allocation-free. Pooled
IValueTaskSource<TResult> instances are reused to minimize allocation pressure across
repeated lock operations.
private readonly AsyncSemaphore _semaphore = new AsyncSemaphore(3);
public async Task AccessLimitedResourceAsync(CancellationToken ct)
{
await _semaphore.WaitAsync(ct);
try
{
// Access limited resource (max 3 concurrent)
await DoWorkAsync();
}
finally
{
_semaphore.Release();
}
}
Constructors
AsyncSemaphore(int, bool, IGetPooledManualResetValueTaskSource<bool>?)
Constructs a new AsyncSemaphore instance with the specified initial count.
public AsyncSemaphore(int initialCount, bool runContinuationAsynchronously = true, IGetPooledManualResetValueTaskSource<bool>? pool = null)
Parameters
initialCountintThe initial number of permits available.
runContinuationAsynchronouslyboolIndicates if continuations are forced to run asynchronously.
poolIGetPooledManualResetValueTaskSource<bool>Custom pool for this instance.
Exceptions
- ArgumentOutOfRangeException
Thrown when
initialCountis negative.
Properties
CurrentCount
Gets the current count of available permits.
public int CurrentCount { get; }
Property Value
RunContinuationAsynchronously
Gets or sets whether to force continuations to run asynchronously.
public bool RunContinuationAsynchronously { get; set; }
Property Value
Remarks
When true (default), continuations are queued to the thread pool when a permit is released, preventing the releasing thread from being blocked by continuation execution. When false, continuations may execute synchronously on the releasing thread.
Methods
Release()
Releases a permit back to the semaphore.
public void Release()
Remarks
If any waiters are queued, the next waiter acquires the permit. Otherwise, the available count is incremented.
Release(int)
Releases the specified number of permits back to the semaphore.
public void Release(int releaseCount)
Parameters
releaseCountintThe number of permits to release.
Exceptions
- ArgumentOutOfRangeException
Thrown when
releaseCountis less than 1.
WaitAsync(CancellationToken)
Asynchronously waits to acquire a permit from the semaphore.
public ValueTask WaitAsync(CancellationToken cancellationToken = default)
Parameters
cancellationTokenCancellationTokenThe cancellation token used to cancel the wait.
Returns
Remarks
If a permit is available, the method returns a completed ValueTask. Otherwise, it enqueues a waiter and returns a task that completes when a permit becomes available.
WaitAsync(TimeSpan, CancellationToken)
Asynchronously waits to acquire a permit from the semaphore, or until the specified timeout elapses.
public ValueTask WaitAsync(TimeSpan timeout, CancellationToken cancellationToken = default)
Parameters
timeoutTimeSpanThe maximum time to wait. Use InfiniteTimeSpan to wait indefinitely.
cancellationTokenCancellationTokenThe cancellation token used to cancel the wait.
Returns
Remarks
If a permit is available, the method returns a completed ValueTask immediately without allocating any cancellation infrastructure. A CancellationTokenSource is allocated only when no permit is available and a finite positive timeout is requested; it is disposed automatically when the returned ValueTask is awaited.
Exceptions
- ArgumentOutOfRangeException
Thrown when
timeoutis negative and not equal to InfiniteTimeSpan.- TimeoutException
Thrown when the timeout elapses before a permit becomes available.
- OperationCanceledException
Thrown when
cancellationTokenis cancelled before a permit becomes available.