Table of Contents

Bodu.Threading Namespace

Package
Bodu.Core 1.0.1

Bodu.Threading

Purpose

Bodu.Threading is a set of asynchronous coordination primitives for async/await code - the awaitable counterparts of the BCL synchronous gates in System.Threading. Each type lets a caller wait on a condition without blocking a thread: a held lock can span an await, a signal can be awaited, and a burst of triggers can be coalesced or rate-limited. The primitives own no operating-system handles (those that need explicit teardown implement IDisposable only to fault still-queued waiters at shutdown).

The whole namespace follows two shared conventions. First, acquisition is exposed through a ValueTask that must be awaited exactly once, and a contention-free acquisition completes synchronously with no allocation. Second, success wins over cancellation: an acquisition that can be granted immediately is granted even when the supplied CancellationToken is already canceled - the token only cancels an acquisition that must queue, and cancellation removes only the calling waiter. Waiters that do queue are released in strict FIFO order, with continuations scheduled asynchronously so a releasing thread is never hijacked to run a waiter inline.

Static documentation

  • Introduction - the Bodu.Core headline types and the scenarios the library covers.
  • Async coordination primitives - each primitive mapped to its synchronous BCL analogue, with a compiling pattern per type.

Key types

Locks and gates

  • AsyncLock - a non-reentrant async mutex. LockAsync() returns a AsyncLock.Releaser whose disposal releases the lock; scope it with using (await gate.LockAsync()). Implements IDisposable.
  • AsyncSemaphore - an async counting semaphore for bounded concurrency. WaitAsync() / Release() for manual permit management, or LockAsync() for a disposable AsyncSemaphore.Releaser; ctors take (initialCount) or (initialCount, maxCount).
  • AsyncReaderWriterLock - a writer-preferring reader/writer lock. ReaderAsync() admits many concurrent readers; WriterAsync() grants one exclusive writer. Both yield an idempotent AsyncReaderWriterLock.Releaser. Implements IDisposable.
  • RateGate - a synchronous leading-edge admission gate. TryInvoke() returns whether a call is admitted this interval; TimeUntilNext reports the remaining cool-down.

Events

  • AsyncAutoResetEvent - auto-reset signal: each Set() releases exactly one waiter and reverts to unsignaled (latching at most one pending signal). Wait with WaitAsync().
  • AsyncManualResetEvent - manual-reset gate: Set() releases all current and future waiters, Reset() closes the gate again, IsSet reports state.
  • AsyncCountdownEvent - fan-in countdown: Signal() decrements the count, AddCount() / TryAddCount() raise it, and WaitAsync() completes once the count reaches zero. Not resettable.

Lazy initialization

  • AsyncLazy<T> - runs an initializer at most once and caches the resulting Task<TResult>, shared by every awaiter. Awaitable directly (await lazy) or via Value / GetValueAsync(token); constructed from a Func<T> (offloaded to the thread pool) or a Func<Task<T>>.

Coalescing and rate limiting

  • AsyncDebouncer - coalesces a burst of Invoke() triggers into a single callback that runs once a quiet period elapses. FlushAsync() runs a pending invocation now, Cancel() discards it, DrainAsync() awaits in-flight work; overlap behavior is set by AsyncDebouncerExecutionPolicy. Implements IDisposable.
  • AsyncDebouncerExecutionPolicy - the overlap policy: QueueOneTrailingRun (default), DropWhileRunning, CancelAndRestart, AllowOverlap.

Example

using Bodu.Threading;

// Mutual exclusion: a held lock may span an await.
private readonly AsyncLock _mutex = new();

public async Task UpdateAsync()
{
    using (await _mutex.LockAsync())
    {
        // Exclusive section; safe to await here.
        await SomeOperationAsync();
    }
}
using Bodu.Threading;

// Bounded concurrency: at most four operations run at once.
private readonly AsyncSemaphore _throttle = new(initialCount: 4);

public async Task DownloadAsync(Uri uri)
{
    using (await _throttle.LockAsync())
    {
        await HttpGetAsync(uri);
    }
}

Notes

Classes

AsyncAutoResetEvent

Provides an asynchronous auto-reset signaling primitive: each call to Set() releases exactly one waiter and then automatically returns to the unsignaled state.

AsyncCountdownEvent

Provides an asynchronous countdown synchronization primitive that becomes signaled once its count reaches zero, releasing all waiters.

AsyncDebouncer

Coalesces a rapid burst of triggers into a single asynchronous invocation that runs once a quiet period has elapsed since the most recent trigger.

AsyncLazy<T>

Provides support for asynchronous lazy initialization: a value is produced at most once, on first access, and the resulting task is cached and shared by every awaiter.

AsyncLock

Provides an asynchronous, non-reentrant mutual-exclusion primitive whose acquisition can be awaited without blocking a thread.

AsyncManualResetEvent

Provides an asynchronous, manually reset signaling primitive: once set, every current and future waiter is released until the event is explicitly reset.

AsyncReaderWriterLock

Provides an asynchronous, writer-preferring reader/writer lock whose acquisitions can be awaited without blocking a thread.

AsyncSemaphore

Provides a lightweight asynchronous counting semaphore that admits a bounded number of concurrent holders and releases waiters in strict first-in, first-out order.

RateGate

Provides a synchronous, leading-edge admission gate that admits at most one invocation per fixed interval, dropping any calls that arrive while the cool-down window opened by the previous admitted call is still open.

Structs

AsyncLock.Releaser

Represents an acquired AsyncLock. Disposing the releaser releases the lock.

AsyncReaderWriterLock.Releaser

Represents acquired access to an AsyncReaderWriterLock. Disposing the releaser releases the read or write access it represents.

AsyncSemaphore.Releaser

Represents a permit taken from an AsyncSemaphore. Disposing the releaser returns the permit.

Enums

AsyncDebouncerExecutionPolicy

Specifies how an AsyncDebouncer behaves when a debounced run becomes due while a previous callback invocation is still in flight.