Table of Contents

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

initialCount int

The initial number of permits available.

runContinuationAsynchronously bool

Indicates if continuations are forced to run asynchronously.

pool IGetPooledManualResetValueTaskSource<bool>

Custom pool for this instance.

Exceptions

ArgumentOutOfRangeException

Thrown when initialCount is negative.

Properties

CurrentCount

Gets the current count of available permits.

public int CurrentCount { get; }

Property Value

int

RunContinuationAsynchronously

Gets or sets whether to force continuations to run asynchronously.

public bool RunContinuationAsynchronously { get; set; }

Property Value

bool

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

releaseCount int

The number of permits to release.

Exceptions

ArgumentOutOfRangeException

Thrown when releaseCount is less than 1.

WaitAsync(CancellationToken)

Asynchronously waits to acquire a permit from the semaphore.

public ValueTask WaitAsync(CancellationToken cancellationToken = default)

Parameters

cancellationToken CancellationToken

The cancellation token used to cancel the wait.

Returns

ValueTask

A ValueTask that completes when a permit is acquired.

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

timeout TimeSpan

The maximum time to wait. Use InfiniteTimeSpan to wait indefinitely.

cancellationToken CancellationToken

The cancellation token used to cancel the wait.

Returns

ValueTask

A ValueTask that completes when a permit is acquired.

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 timeout is negative and not equal to InfiniteTimeSpan.

TimeoutException

Thrown when the timeout elapses before a permit becomes available.

OperationCanceledException

Thrown when cancellationToken is cancelled before a permit becomes available.