This library provides a single primitive: AsyncLock.
AsyncLock is built to be the fastest possible correct mutex for real-world .NET systems.
It provides the following guarantees:
- Fully supports cancellation before acquisition and while waiting for both async and sync callers.
- Cancelled waiters are removed immediately, never resumed, and never leaked — with zero impact on the fast path.
- Async and synchronous callers share the same mutex.
- Ordering is preserved without adapters, wrappers, or duplicated synchronization primitives.
- Uncontended acquisition is as close to a single atomic operation as possible
- No allocations, tasks, or state machines unless contention occurs
- Cancellation and disposal logic are completely excluded from the fast path
- Deterministic behavior under contention
dotnet add package Soenneker.Asyncs.Locksawait using (await _lock.Lock(ct))
{
// critical section
}using (_lock.LockSync())
{
// critical section
}if (_lock.TryLock(out var releaser))
{
using (releaser)
{
// critical section
}
}| Method | Mean | Error | StdDev | Median | Ratio | Allocated |
|---|---|---|---|---|---|---|
| Soenneker.Asyncs.Lock | 10.06 ns | 0.212 ns | 0.393 ns | 10.01 ns | baseline | - |
| SemaphoreSlim | 19.17 ns | 0.406 ns | 0.360 ns | 19.10 ns | 1.91x slower | - |
| Nito.AsyncEx.AsyncLock | 55.32 ns | 1.078 ns | 2.645 ns | 54.81 ns | 5.51x slower | 320 B |
| Method | Mean | Error | StdDev | Median | Ratio | Allocated |
|---|---|---|---|---|---|---|
| Soenneker.Asyncs.Lock | 8.09 ns | 0.179 ns | 0.314 ns | 8.03 ns | baseline | - |
| SemaphoreSlim | 19.49 ns | 0.403 ns | 0.727 ns | 19.09 ns | 2.41x slower | - |
| Nito.AsyncEx.AsyncLock | 48.43 ns | 1.005 ns | 2.915 ns | 48.05 ns | 6.00x slower | 320 B |