CryptoHives.Foundation.Threading Package
Overview
The Threading package provides high-performance, pooled async synchronization primitives for .NET applications with high throughput workloads, where many small memory allocations matter. All synchronization primitives leverage ValueTask and pooled IValueTaskSource for efficient async operations. They are not meant to replace but to complement existing popular async libraries.
There are already a number of popular async synchronization libraries available for .NET, but many of these incur significant memory allocations under high contention due to per-waiter Task and TaskCompletionSource allocations or cancellation handling.
In recent years, the introduction of ValueTask and IValueTaskSource has enabled the creation of low-allocation async primitives that can be pooled and reused to minimize allocations in high-throughput scenarios.
This library is the result of the research done by the Keepers of the CryptoHives into building efficient, pooled async synchronization primitives that leverage these modern .NET features.
By demand, more primitives might be added in the future.
Key Features
- Pooled Primitives: Synchronization objects backed by object pools
- ValueTask-based: Low-allocation async operations
- High Performance: Optimized for concurrent access patterns
- Thread-safe: All operations are thread-safe
- Ease of use: Drop-in replacement to replace other popular libraries by changing the namespace
- Cancellation support: Full
CancellationTokensupport across all primitives - Configurable continuations: Control synchronous vs asynchronous continuation execution
- Custom ObjectPools: Supply your own object pools for fine-grained control
- Included Analyzers: Roslyn analyzers automatically detect common ValueTask misuse patterns
Installation
dotnet add package CryptoHives.Foundation.Threading
Note: This package includes Threading Analyzers automatically. The analyzers are transitive, so any project that references a project using this package will also benefit from the ValueTask misuse detection at compile time.
Namespaces
Async Synchronization Primitives
using CryptoHives.Foundation.Threading.Async.Pooled;
Pooling Infrastructure
using CryptoHives.Foundation.Threading.Pools;
Classes
Synchronization Primitives
| Class | Description | Documentation |
|---|---|---|
| AsyncLock | Pooled async mutual exclusion lock | Details |
| AsyncAutoResetEvent | Pooled async auto-reset event (one waiter per signal) | Details |
| AsyncManualResetEvent | Pooled async manual-reset event (all waiters per signal) | Details |
| AsyncSemaphore | Pooled async semaphore with configurable permit count | Details |
| AsyncCountdownEvent | Pooled async countdown event (signals when count reaches zero) | Details |
| AsyncBarrier | Pooled async barrier (synchronizes multiple participants) | Details |
| AsyncReaderWriterLock | Pooled async reader-writer lock (multiple readers or single writer) | Details |
Pooling Support Classes
| Class | Description | Namespace |
|---|---|---|
IGetPooledManualResetValueTaskSource<T> |
Interface to get pooled IValueTaskSource<T> implementations (providers return PooledManualResetValueTaskSource<T> instances) |
CryptoHives.Foundation.Threading.Pools |
ManualResetValueTaskSource<T> |
Abstract base for pooled IValueTaskSource<T> implementations |
CryptoHives.Foundation.Threading.Pools |
PooledManualResetValueTaskSource<T> |
Pooled IValueTaskSource<T> implementation with automatic pool return |
CryptoHives.Foundation.Threading.Pools |
LocalManualResetValueTaskSource<T> |
Object-local IValueTaskSource<T> without pool integration |
CryptoHives.Foundation.Threading.Pools |
PooledValueTaskSourceObjectPolicy<T> |
Object pool policy for PooledManualResetValueTaskSource<T> |
CryptoHives.Foundation.Threading.Pools |
ValueTaskSourceObjectPool<T> |
Specialized provider that implements IGetPooledManualResetValueTaskSource<T> and returns pooled task sources |
CryptoHives.Foundation.Threading.Pools |
ValueTaskSourceObjectPools |
Static helper with shared pool instances and constants | CryptoHives.Foundation.Threading.Pools |
⚠️ Known Issues and Caveats
- Strictly only await a ValueTask once. An additional await or AsTask() may throw an
InvalidOperationException. - Strictly only use AsTask() once, and only if you have to. An additional await or AsTask() may throw an
InvalidOperationException. Adds also a Task allocation on contention. - RunContinuationsAsynchronously is by default enabled. In rare cases perf degradation may occur if the Task derived from a ValueTask is not immediately awaited (see benchmarks).
- Pool Exhaustion: In extreme high-throughput scenarios with many waiters, the pool may exhaust. Monitor and adjust usage patterns accordingly. Use a custom pool if necessary.
- Always await a ValueTask or AsTask() waiter primitive, or the
IValueTaskSourceis not returned to the pool. - AsTask() Performance Warning: When
RunContinuationAsynchronously=true(default), storing the result ofAsTask()before signaling causes severe performance degradation (10x-100x slower). Always awaitValueTaskdirectly when possible.
Benchmarks
Microbenchmarks and contention tests with a discussion of the performance characteristics are available to validate performance and allocation characteristics.
Please be aware that not all new replacement classes behave better than existing popular implementations in all scenarios; But most of the time they do.
For example the AsyncManualResetEvent implementation has an overhead of a IValueTaskSource per waiter, because ValueTask cannot be awaited by multiple instances. In contrast to a Task-based implementation all waiters can share the same wake-up Task and TaskCompletionSource.
See the Benchmarks overview:
Run reports are stored under:
tests/Threading/BenchmarkDotNet.Artifacts/results/
Quick Examples
AsyncLock
private readonly AsyncLock _lock = new AsyncLock();
public async Task AccessSharedResourceAsync(CancellationToken ct)
{
using (await _lock.LockAsync(ct))
{
// Critical section - only one task at a time
await ModifySharedStateAsync();
}
}
AsyncAutoResetEvent
private readonly AsyncAutoResetEvent _event = new AsyncAutoResetEvent(false);
// Producer
public async Task ProduceAsync()
{
await ProduceItemAsync();
_event.Set(); // Signal one waiter
}
// Consumer
public async Task ConsumeAsync(CancellationToken ct)
{
await _event.WaitAsync(ct); // Wait for signal
await ProcessItemAsync();
}
AsyncManualResetEvent
private readonly AsyncManualResetEvent _event = new AsyncManualResetEvent(false);
// Controller
public void SignalReady()
{
_event.Set(); // Signal all waiters
}
// Worker
public async Task WaitForReadyAsync(CancellationToken ct)
{
await _event.WaitAsync(ct); // Multiple tasks can wait
await DoWorkAsync();
}
AsyncSemaphore
private readonly AsyncSemaphore _semaphore = new AsyncSemaphore(3);
// Limited concurrent access
public async Task AccessLimitedResourceAsync(CancellationToken ct)
{
await _semaphore.WaitAsync(ct);
try
{
// Max 3 concurrent tasks can access this section
await AccessResourceAsync();
}
finally
{
_semaphore.Release();
}
}
AsyncCountdownEvent
private readonly AsyncCountdownEvent _countdown = new AsyncCountdownEvent(3);
// Coordinator
public async Task WaitForWorkersAsync(CancellationToken ct)
{
await _countdown.WaitAsync(ct);
// All workers have signaled
}
// Worker
public void WorkerCompleted()
{
_countdown.Signal();
}
AsyncBarrier
private readonly AsyncBarrier _barrier = new AsyncBarrier(3);
// Participant
public async Task ParticipantWorkAsync(CancellationToken ct)
{
await DoPhase1WorkAsync();
await _barrier.SignalAndWaitAsync(ct); // Wait for all participants
await DoPhase2WorkAsync();
}
AsyncReaderWriterLock
private readonly AsyncReaderWriterLock _rwLock = new AsyncReaderWriterLock();
// Reader
public async Task ReadAsync(CancellationToken ct)
{
using (await _rwLock.ReaderLockAsync(ct))
{
// Multiple readers can hold the lock concurrently
await ReadDataAsync();
}
}
// Writer
public async Task WriteAsync(CancellationToken ct)
{
using (await _rwLock.WriterLockAsync(ct))
{
// Exclusive access
await WriteDataAsync();
}
}
Benefits
Reduced Allocations
- Reuses
IValueTaskSourceinstances from object pools - ValueTask-based APIs avoid Task allocations when operations complete synchronously
- Best case: zero allocation overhead for async state machines
- On latest .NET versions cancellation token registration is allocation free
High Throughput
- Optimized for high-contention scenarios
- Lock-free operations where possible
- Configurable continuation scheduling
Compatibility
- Drop in replacement of existing libraries by changing namespace
- Works with async/await patterns
- Cancellation token support
- ConfigureAwait support
Performance Characteristics
- AsyncLock: O(1) acquire when uncontended, FIFO queue for waiters
- AsyncAutoResetEvent: O(1) Set/Wait, FIFO queue for single waiter release
- AsyncManualResetEvent: O(n) Set broadcast to all n waiters, O(1) Reset
Best Practices
- Determine benefits: Are pooled primitives and ValueTask beneficial for your workload?
- Reuse instances: Create synchronization primitives once and reuse them
- Do not reuse ValueTask: Always await or AsTask() only once per ValueTask instance
- Always Await ValueTask and Task: If not awaited, resources are not returned to the pool
- Avoid holding locks: Keep critical sections as short as possible
- Use cancellation: Always pass CancellationToken for long waits
- ConfigureAwait(false): Use in library code to avoid context capture
- Avoid AsTask() before signaling: When
RunContinuationAsynchronously=true, this causes severe performance degradation
Common Patterns
Producer-Consumer
private readonly AsyncAutoResetEvent _itemAvailable = new AsyncAutoResetEvent(false);
private readonly Queue<Item> _queue = new();
public async Task ProducerAsync(Item item)
{
_queue.Enqueue(item);
_itemAvailable.Set();
}
public async Task<Item> ConsumerAsync(CancellationToken ct)
{
await _itemAvailable.WaitAsync(ct);
return _queue.Dequeue();
}
Async Initialization
private readonly AsyncManualResetEvent _initialized = new AsyncManualResetEvent(false);
public async Task InitializeAsync()
{
await DoInitializationAsync();
_initialized.Set();
}
public async Task UseServiceAsync(CancellationToken ct)
{
await _initialized.WaitAsync(ct);
// Service is now initialized
}
Rate Limiting
private readonly AsyncLock _rateLimiter = new AsyncLock();
public async Task<T> RateLimitedOperationAsync<T>(Func<Task<T>> operation, CancellationToken ct)
{
using (await _rateLimiter.LockAsync(ct))
{
await Task.Delay(100, ct); // Rate limit
return await operation();
}
}
Comparison with Standard Library
| Feature | Threading Package | System.Threading |
|---|---|---|
| Allocation overhead | Minimal (pooled) | Higher (per operation) |
| ValueTask support | Yes | Partial |
| Pooling | Built-in | Manual |
| Performance | Optimized for high-throughput | Standard |
| Cancellation | Full support | Varies |
Advanced: Custom Pooling
You can provide a custom provider that implements IGetPooledManualResetValueTaskSource<T> for fine-grained control over pool behavior. The built-in ValueTaskSourceObjectPool<T> implements this interface and can be used directly.
using CryptoHives.Foundation.Threading.Pools;
using Microsoft.Extensions.ObjectPool;
// Create a custom pool provider (ValueTaskSourceObjectPool implements IGetPooledManualResetValueTaskSource<T>)
var customPolicy = new PooledValueTaskSourceObjectPolicy<bool>();
var customPool = new ValueTaskSourceObjectPool<bool>(customPolicy, maximumRetained: 64);
// Use custom provider with event
var evt = new AsyncAutoResetEvent(
initialState: false,
runContinuationAsynchronously: true,
pool: customPool); // accepts any IGetPooledManualResetValueTaskSource<bool>
ValueTaskSource Details
The ManualResetValueTaskSource<T> abstract base class provides:
Versionproperty for versioning supportRunContinuationsAsynchronouslyproperty to control continuation schedulingCancellationTokenandCancellationTokenRegistrationfor cancellation supportSetResult()andSetException()for completionTryReset()for pool reuse
The PooledManualResetValueTaskSource<T> sealed implementation:
- Automatically returns to pool after
GetResult()is called - Integrates with
IResettablefor pool compatibility - Manages cancellation token registration lifecycle
See Also
- Threading Analyzers - Roslyn analyzers for detecting ValueTask misuse
- Memory Package
- AsyncAutoResetEvent - Auto-reset event variant
- AsyncManualResetEvent - Manual-reset event variant
- AsyncReaderWriterLock - Async reader-writer lock
- AsyncLock - Async mutual exclusion lock
- AsyncCountdownEvent - Async countdown event
- AsyncBarrier - Async barrier synchronization primitive
- AsyncSemaphore - Async semaphore primitive
- Benchmarks - Benchmark description
© 2026 The Keepers of the CryptoHives