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
initialCountintThe initial count for the countdown event.
runContinuationAsynchronouslyboolIndicates if continuations are forced to run asynchronously.
poolIGetPooledManualResetValueTaskSource<bool>Custom pool for this instance.
Exceptions
- ArgumentOutOfRangeException
Thrown when
initialCountis less than or equal to zero.
Properties
CurrentCount
Gets the current count remaining.
public int CurrentCount { get; }
Property Value
InitialCount
Gets the initial count that was set when the countdown event was created.
public int InitialCount { get; }
Property Value
IsSet
Gets whether the countdown has reached zero.
public bool IsSet { get; }
Property Value
RunContinuationAsynchronously
Gets or sets whether to force continuations to run asynchronously.
public bool RunContinuationAsynchronously { get; set; }
Property Value
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
signalCountintThe number to add to the countdown.
Exceptions
- ArgumentOutOfRangeException
Thrown when
signalCountis 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
countintThe new count value. If not specified, resets to initial count.
Exceptions
- ArgumentOutOfRangeException
Thrown when
countis 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
signalCountintThe number of signals to decrement.
Exceptions
- ArgumentOutOfRangeException
Thrown when
signalCountis 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
cancellationTokenCancellationTokenThe cancellation token used to cancel the wait.
Returns
TryAddCount(int)
Attempts to add to the countdown.
public bool TryAddCount(int signalCount = 1)
Parameters
signalCountintThe number to add to the countdown.
Returns
WaitAsync(CancellationToken)
Asynchronously waits for the countdown to reach zero.
public ValueTask WaitAsync(CancellationToken cancellationToken = default)
Parameters
cancellationTokenCancellationTokenThe cancellation token used to cancel the wait.
Returns
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
timeoutTimeSpanThe maximum time to wait. Use InfiniteTimeSpan to wait indefinitely.
cancellationTokenCancellationTokenThe cancellation token used to cancel the wait.
Returns
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
timeoutis negative and not equal to InfiniteTimeSpan.- TimeoutException
Thrown when the timeout elapses before the countdown reaches zero.
- OperationCanceledException
Thrown when
cancellationTokenis cancelled before the countdown reaches zero.