Table of Contents

Class AsyncCountdownEvent

Namespace
CryptoHives.Foundation.Threading.Async.Pooled
Assembly
CryptoHives.Foundation.Threading.dll

An async version of CountdownEvent which uses a pooled approach to implement waiters for ValueTask to reduce memory allocations.

public sealed class AsyncCountdownEvent
Inheritance
AsyncCountdownEvent
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 the countdown reaches zero. When set to true (default), continuations are forced to queue to the thread pool.

Allocation Behavior: Immediate acquisitions are completely allocation-free using atomic operations. When the countdown 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 AsyncCountdownEvent _countdown = new AsyncCountdownEvent(3);

public async Task WaitForAllWorkersAsync(CancellationToken ct) { await _countdown.WaitAsync(ct); // All workers have signaled }

public void WorkerCompleted() { _countdown.Signal(); }

Constructors

AsyncCountdownEvent(int, bool, IGetPooledManualResetValueTaskSource<bool>?)

Constructs a new AsyncCountdownEvent instance with the specified initial count.

public AsyncCountdownEvent(int initialCount, bool runContinuationAsynchronously = true, IGetPooledManualResetValueTaskSource<bool>? pool = null)

Parameters

initialCount int

The initial count for the countdown event.

runContinuationAsynchronously bool

Indicates if continuations are forced to run asynchronously.

pool IGetPooledManualResetValueTaskSource<bool>

Custom pool for this instance.

Exceptions

ArgumentOutOfRangeException

Thrown when initialCount is less than or equal to zero.

Properties

CurrentCount

Gets the current count remaining.

public int CurrentCount { get; }

Property Value

int

InitialCount

Gets the initial count that was set when the countdown event was created.

public int InitialCount { get; }

Property Value

int

IsSet

Gets whether the countdown has reached zero.

public bool IsSet { get; }

Property Value

bool

RunContinuationAsynchronously

Gets or sets whether to force continuations to run asynchronously.

public bool RunContinuationAsynchronously { get; set; }

Property Value

bool

Methods

AddCount()

Increments the countdown by one.

public void AddCount()

Exceptions

InvalidOperationException

Thrown when the countdown has already reached zero.

AddCount(int)

Increments the countdown by the specified amount.

public void AddCount(int signalCount)

Parameters

signalCount int

The number to add to the countdown.

Exceptions

ArgumentOutOfRangeException

Thrown when signalCount is less than 1.

InvalidOperationException

Thrown when the countdown has already reached zero.

Reset(int)

Resets the countdown to the specified count, or to the initial count if not specified.

public void Reset(int count = 0)

Parameters

count int

The new count value. If not specified, resets to initial count.

Exceptions

ArgumentOutOfRangeException

Thrown when count is less than or equal to zero.

Signal()

Decrements the countdown by one.

public void Signal()

Remarks

When the countdown reaches zero, all waiting tasks are signaled.

Exceptions

InvalidOperationException

Thrown when the countdown has already reached zero.

Signal(int)

Decrements the countdown by the specified amount.

public void Signal(int signalCount)

Parameters

signalCount int

The number of signals to decrement.

Exceptions

ArgumentOutOfRangeException

Thrown when signalCount is less than 1.

InvalidOperationException

Thrown when the signal count would cause the countdown to go below zero.

SignalAndWaitAsync(CancellationToken)

Signals the countdown and waits for it to reach zero.

public ValueTask SignalAndWaitAsync(CancellationToken cancellationToken = default)

Parameters

cancellationToken CancellationToken

The cancellation token used to cancel the wait.

Returns

ValueTask

A ValueTask that completes when the countdown reaches zero.

TryAddCount(int)

Attempts to add to the countdown.

public bool TryAddCount(int signalCount = 1)

Parameters

signalCount int

The number to add to the countdown.

Returns

bool

true if the add was successful; otherwise, false.

WaitAsync(CancellationToken)

Asynchronously waits for the countdown to reach zero.

public ValueTask WaitAsync(CancellationToken cancellationToken = default)

Parameters

cancellationToken CancellationToken

The cancellation token used to cancel the wait.

Returns

ValueTask

A ValueTask that completes when the countdown reaches zero.

Remarks

If the countdown has already reached zero, the method returns a completed ValueTask. Otherwise, it enqueues a waiter and returns a task that completes when the countdown reaches zero.

WaitAsync(TimeSpan, CancellationToken)

Asynchronously waits for the countdown to reach zero, 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 the countdown reaches zero.

Remarks

If the countdown has already reached zero, the method returns a completed ValueTask immediately without allocating any cancellation infrastructure. A CancellationTokenSource is allocated only when the countdown is non-zero 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 the countdown reaches zero.

OperationCanceledException

Thrown when cancellationToken is cancelled before the countdown reaches zero.