CryptoHives.Foundation.Threading
0.6.79
dotnet add package CryptoHives.Foundation.Threading --version 0.6.79
NuGet\Install-Package CryptoHives.Foundation.Threading -Version 0.6.79
<PackageReference Include="CryptoHives.Foundation.Threading" Version="0.6.79" />
<PackageVersion Include="CryptoHives.Foundation.Threading" Version="0.6.79" />
<PackageReference Include="CryptoHives.Foundation.Threading" />
paket add CryptoHives.Foundation.Threading --version 0.6.79
#r "nuget: CryptoHives.Foundation.Threading, 0.6.79"
#:package CryptoHives.Foundation.Threading@0.6.79
#addin nuget:?package=CryptoHives.Foundation.Threading&version=0.6.79
#tool nuget:?package=CryptoHives.Foundation.Threading&version=0.6.79
π‘οΈ CryptoHives Open Source Initiative π
An open, community-driven collection of cryptography and performance libraries for the .NET ecosystem, maintained by The Keepers of the CryptoHives.
π§΅ CryptoHives.Foundation.Threading
Pooled async synchronization primitives for .NET, built to keep Task and TaskCompletionSource<T> allocations off the hot path.
π§± Classes
Synchronization Primitives
Namespace: CryptoHives.Foundation.Threading.Async.Pooled
| Class | Description |
|---|---|
| AsyncLock | Pooled async mutual exclusion lock |
| AsyncKeyedLock<TKey> | Pooled per-key async exclusive lock (different keys never block each other) |
| AsyncAutoResetEvent | Pooled async auto-reset event (one waiter per signal) |
| AsyncManualResetEvent | Pooled async manual-reset event (all waiters per signal) |
| AsyncSemaphore | Pooled async semaphore with configurable permit count |
| AsyncCountdownEvent | Pooled async countdown event (signals when count reaches zero) |
| AsyncBarrier | Pooled async barrier (synchronizes multiple participants) |
| AsyncReaderWriterLock | Pooled async reader-writer lock (multiple readers or single writer) |
All primitives are backed by ObjectPool<T> and return ValueTask<T>, which keeps per-operation allocations out of high-throughput code paths.
Pooling Support Classes
Namespace: CryptoHives.Foundation.Threading.Pools
| Class | Description | Namespace |
|---|---|---|
IGetPooledManualResetValueTaskSource<T> |
Interface for obtaining pooled IValueTaskSource<T> implementations (providers return PooledManualResetValueTaskSource<T> instances) |
|
ManualResetValueTaskSource<T> |
Abstract base for pooled IValueTaskSource<T> implementations |
|
PooledManualResetValueTaskSource<T> |
Pooled IValueTaskSource<T> implementation with automatic pool return |
|
LocalManualResetValueTaskSource<T> |
Object-local IValueTaskSource<T> without pool integration |
|
PooledValueTaskSourceObjectPolicy<T> |
Object pool policy for PooledManualResetValueTaskSource<T> |
|
ValueTaskSourceObjectPool<T> |
Specialized provider that implements IGetPooledManualResetValueTaskSource<T> and returns pooled task sources |
|
ValueTaskSourceObjectPools |
Static helper with shared pool instances and constants |
Note: This package no longer bundles CryptoHives.Foundation.Threading.Analyzers automatically. Install it separately if you want the Roslyn analyzers alongside the Threading library.
β¨ Key Features
- Pooled primitives β synchronization objects backed by
Microsoft.Extensions.ObjectPool ValueTask-based APIs β minimal to no allocations thanks to object pooling; pooled primitives retain a bounded number of objects, so allocation-free behaviour holds while the workload fits the pool (see Sizing the caches forAsyncKeyedLock<TKey>, the one primitive where the bound is worth sizing deliberately)CancellationTokensupport β full cancellation across all primitives, allocation-free on modern .NETConfigureAwaitsupport β works naturally with.ConfigureAwait(false)in library code- Timeouts β every lock acquisition method accepts a timeout; a timed-out wait throws
TimeoutException, a cancelled one throwsOperationCanceledException - Configurable continuations β control whether continuations run synchronously or asynchronously
- Custom pools β supply your own
IGetPooledManualResetValueTaskSource<T>(orObjectPool<T>) for fine-grained control - Drop-in replacement β swap the namespace, keep the same
using-based patterns - Optional analyzers β ValueTask misuse caught at compile time via the separate
Threading.Analyzerspackage
π₯ Installation
dotnet add package CryptoHives.Foundation.Threading
π‘ Quick Examples
Mutual Exclusion β AsyncLock
using CryptoHives.Foundation.Threading.Async.Pooled;
private readonly AsyncLock _lock = new();
public async Task DoWorkAsync(CancellationToken ct)
{
using (await _lock.LockAsync(ct).ConfigureAwait(false))
{
// Critical section β only one task at a time
await ModifySharedStateAsync().ConfigureAwait(false);
}
}
Per-Key Exclusion β AsyncKeyedLock<TKey>
private readonly AsyncKeyedLock<string> _locksByAccount = new();
public async Task TransferAsync(string accountId, CancellationToken ct)
{
using (await _locksByAccount.LockAsync(accountId, ct).ConfigureAwait(false))
{
// Only one operation per accountId; other accounts proceed in parallel
await ApplyTransferAsync(accountId).ConfigureAwait(false);
}
}
Sizing the caches
AsyncKeyedLock<TKey> is allocation-free while a workload fits inside two caps, and degrades
gracefully β not catastrophically β past either. Both default to 128.
| Cap | Bounds | Past the cap |
|---|---|---|
maxIdleEntries |
key cardinality β the keys that are hot at the same time | Each acquisition of an unmapped key takes over the least recently idled entry and allocates a fresh dictionary node (~48 B/key). The entry itself is still reused. |
maxRetainedWaiters |
simultaneous contention β waiters queued at one moment, summed across all keys | Each waiter beyond the cap is allocated and then discarded rather than returned to the pool. |
A released key stays mapped as an idle entry rather than being torn down, so locking and releasing
the same key repeatedly allocates nothing at all. That is what makes the common case free β and
it is also why maxIdleEntries should span the hot key set rather than the total number of distinct
keys ever seen. Retention is bounded by the keys actually used, so a generous cap costs a lock with
few keys nothing.
Each entry supplies one waiter itself and the pool covers the rest, so a lock with 4 keys and 100 waiters behind each needs 396 pooled waiters β at the default, such a burst allocates on 268 of them.
// Sized to the workload: ~2000 hot tenants, up to ~500 waiters queued at peak
private readonly AsyncKeyedLock<string> _locksByTenant =
new(maxIdleEntries: 2048, maxRetainedWaiters: 512);
Note: the default waiter pool is shared process-wide per closed
TKey, so everyAsyncKeyedLock<string>in the process draws on one budget. PassingmaxRetainedWaitersis also what gives an instance a private pool.
Producer-Consumer β AsyncAutoResetEvent
private readonly AsyncAutoResetEvent _itemReady = new(initialState: false);
private readonly Queue<Item> _queue = new();
// Producer
public void Enqueue(Item item)
{
_queue.Enqueue(item);
_itemReady.Set(); // Releases exactly one waiter
}
// Consumer
public async Task<Item> DequeueAsync(CancellationToken ct)
{
await _itemReady.WaitAsync(ct).ConfigureAwait(false);
return _queue.Dequeue();
}
Broadcast β AsyncManualResetEvent
private readonly AsyncManualResetEvent _ready = new(set: false);
public async Task InitializeAsync()
{
await LoadConfigurationAsync().ConfigureAwait(false);
_ready.Set(); // All waiters are released at once
}
public async Task UseServiceAsync(CancellationToken ct)
{
await _ready.WaitAsync(ct).ConfigureAwait(false);
// Service is initialized
}
Bounded Concurrency β AsyncSemaphore
private readonly AsyncSemaphore _semaphore = new(initialCount: 4);
public async Task FetchAsync(CancellationToken ct)
{
await _semaphore.WaitAsync(ct).ConfigureAwait(false);
try
{
await CallExternalServiceAsync().ConfigureAwait(false);
}
finally
{
_semaphore.Release();
}
}
Read-Write Separation β AsyncReaderWriterLock
private readonly AsyncReaderWriterLock _rwLock = new();
public async Task<Data> ReadAsync(CancellationToken ct)
{
using (await _rwLock.ReaderLockAsync(ct).ConfigureAwait(false))
return _cache.Get();
}
public async Task WriteAsync(Data data, CancellationToken ct)
{
using (await _rwLock.WriterLockAsync(ct).ConfigureAwait(false))
_cache.Set(data);
}
Custom Pool
using CryptoHives.Foundation.Threading.Pools;
var policy = new PooledValueTaskSourceObjectPolicy<bool>();
var pool = new ValueTaskSourceObjectPool<bool>(policy, maximumRetained: 64);
var evt = new AsyncAutoResetEvent(
initialState: false,
runContinuationAsynchronously: true,
pool: pool);
π ValueTask Contract
- Await a
ValueTaskexactly once. A secondawaitorAsTask()call may throwInvalidOperationException. - Avoid calling
AsTask()before the primitive signals. WithRunContinuationsAsynchronously=true(the default), storing the result ofAsTask()too early causes a severe performance hit. Await theValueTaskdirectly wherever you can. - Always await or discard a waiter. If it's left unconsumed, the underlying
IValueTaskSourcenever makes it back to the pool.
The separate Threading.Analyzers package enforces these rules at compile time.
π Documentation
| Resource | Link |
|---|---|
| Full package documentation | cryptohives.github.io/Foundation/packages/threading |
| API reference | cryptohives.github.io/β¦/api/β¦Threading.Async.Pooled |
| Benchmarks | cryptohives.github.io/Foundation/packages/threading/benchmarks |
| Threading.Analyzers | cryptohives.github.io/Foundation/packages/threading.analyzers |
| Source repository | github.com/CryptoHives/Foundation |
π¨ Security Policy
If you discover a vulnerability, please don't open a public issue β follow the process on the CryptoHives Security Page instead.
βοΈ License
MIT β Β© 2026 The Keepers of the CryptoHives
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 is compatible. |
| .NET Framework | net461 was computed. net462 is compatible. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETFramework 4.6.2
- Microsoft.Bcl.AsyncInterfaces (>= 10.0.11)
- Microsoft.Bcl.HashCode (>= 6.0.0)
- Microsoft.Bcl.TimeProvider (>= 10.0.11)
- Microsoft.Extensions.ObjectPool (>= 10.0.11)
- System.Memory (>= 4.6.3)
- System.Threading.Tasks.Extensions (>= 4.6.3)
-
.NETStandard 2.0
- Microsoft.Bcl.AsyncInterfaces (>= 10.0.11)
- Microsoft.Bcl.HashCode (>= 6.0.0)
- Microsoft.Bcl.TimeProvider (>= 10.0.11)
- Microsoft.Extensions.ObjectPool (>= 10.0.11)
- System.Memory (>= 4.6.3)
- System.Threading.Tasks.Extensions (>= 4.6.3)
-
.NETStandard 2.1
- Microsoft.Bcl.AsyncInterfaces (>= 10.0.11)
- Microsoft.Bcl.TimeProvider (>= 10.0.11)
- Microsoft.Extensions.ObjectPool (>= 10.0.11)
-
net10.0
- Microsoft.Extensions.ObjectPool (>= 10.0.11)
-
net8.0
- Microsoft.Extensions.ObjectPool (>= 10.0.11)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.6.79 | 55 | 8/12/2026 |
| 0.6.51 | 219 | 8/1/2026 |
| 0.6.21 | 278 | 7/4/2026 |
| 0.5.34-preview | 633 | 6/2/2026 |
| 0.5.21-preview | 162 | 5/2/2026 |
| 0.5.13-preview | 114 | 4/2/2026 |
| 0.4.21-preview | 122 | 3/1/2026 |
| 0.4.11-preview | 130 | 2/14/2026 |
| 0.3.19-preview | 127 | 1/26/2026 |
| 0.2.43-preview | 130 | 1/9/2026 |
| 0.2.33-preview | 474 | 12/9/2025 |
| 0.2.30-preview | 386 | 12/8/2025 |
| 0.2.28-preview | 321 | 12/7/2025 |
| 0.2.26-preview | 263 | 12/6/2025 |
| 0.2.22-preview | 612 | 12/1/2025 |
| 0.2.17-preview | 221 | 11/23/2025 |
| 0.2.13-preview | 450 | 11/20/2025 |
| 0.2.11-preview | 1,040 | 11/19/2025 |