Soenneker.Asyncs.Locks
4.0.74
Prefix Reserved
See the version list below for details.
dotnet add package Soenneker.Asyncs.Locks --version 4.0.74
NuGet\Install-Package Soenneker.Asyncs.Locks -Version 4.0.74
<PackageReference Include="Soenneker.Asyncs.Locks" Version="4.0.74" />
<PackageVersion Include="Soenneker.Asyncs.Locks" Version="4.0.74" />
<PackageReference Include="Soenneker.Asyncs.Locks" />
paket add Soenneker.Asyncs.Locks --version 4.0.74
#r "nuget: Soenneker.Asyncs.Locks, 4.0.74"
#:package Soenneker.Asyncs.Locks@4.0.74
#addin nuget:?package=Soenneker.Asyncs.Locks&version=4.0.74
#tool nuget:?package=Soenneker.Asyncs.Locks&version=4.0.74
Soenneker.Asyncs.Locks
A low-allocation mutex shared by asynchronous and synchronous callers.
AsyncLock provides cancellable async acquisition, blocking synchronous acquisition, and a non-blocking try-lock. Contended async waiters use pooled IValueTaskSource instances, while the uncontended path avoids allocating a Task.
Installation
dotnet add package Soenneker.Asyncs.Locks
Asynchronous locking
Keep the lock on the object that owns the protected state and dispose every acquired Releaser:
using Soenneker.Asyncs.Locks;
public sealed class BalanceStore : IAsyncDisposable
{
private readonly AsyncLock _lock = new();
private decimal _balance;
public async ValueTask Add(
decimal amount,
CancellationToken cancellationToken)
{
using Releaser releaser = await _lock.Lock(cancellationToken);
_balance += amount;
}
public ValueTask DisposeAsync() => _lock.DisposeAsync();
}
Releaser implements IDisposable, not IAsyncDisposable, so use using for the acquired token even inside an async method. await using is appropriate for the AsyncLock itself when the owner is disposed asynchronously.
The tokenless overload avoids cancellation registration when cancellation is not needed:
using Releaser releaser = await asyncLock.Lock();
Synchronous locking
Synchronous and asynchronous callers contend for the same mutex:
using Releaser releaser = asyncLock.LockSync(cancellationToken);
// Protected synchronous work
LockSync blocks the calling thread while contended. Do not use it from asynchronous request paths merely to avoid await; use Lock there.
Try without waiting
if (asyncLock.TryLock(out Releaser releaser))
{
using (releaser)
{
// The lock is held here.
}
}
else
{
// Another caller holds or is waiting for the lock, or it was disposed.
}
TryLock returns false instead of waiting. After disposal it also returns false; the blocking acquisition methods throw ObjectDisposedException.
Cancellation
Lock(CancellationToken) and LockSync(CancellationToken) support cancellation before acquisition and while queued. A cancelled waiter does not enter the critical section. Once acquisition succeeds, cancellation does not release the lock; only disposing the returned Releaser does that.
Always keep acquisition outside the protected try/finally or using body so code does not attempt to release a lock it never acquired.
Disposal
The two disposal methods intentionally differ:
| Method | Current holder | Queued and future callers |
|---|---|---|
Dispose() |
May finish and release normally; disposal does not wait. | Queued callers fail and future blocking acquisitions throw. |
DisposeAsync() |
Waits for the current holder to release. | Queued callers fail and future blocking acquisitions throw. |
Neither method forcibly interrupts code already inside the critical section. Dispose the lock only when its owning service is shutting down and no new work should be accepted.
Correctness rules
AsyncLockis not reentrant. Code that already holds it must not acquire it again before releasing it.- Keep critical sections short and avoid calling unknown code while holding the lock.
- Dispose each successful
Releaserexactly once. It is a value type, so copying it and disposing multiple copies releases the mutex more than once and corrupts ownership state. - Do not use the default value of
Releaser; only use values returned by successful acquisition. - A cancellation token controls acquisition only, not work performed after acquisition.
API
| Member | Behavior |
|---|---|
Lock() |
Acquires asynchronously without cancellation registration. |
Lock(CancellationToken) |
Acquires asynchronously with cancellable waiting. |
LockSync(CancellationToken) |
Blocks until acquired or cancelled. |
TryLock(out Releaser) |
Attempts immediate acquisition. |
Dispose() |
Rejects waiters without waiting for the holder. |
DisposeAsync() |
Rejects waiters and waits for the holder to exit. |
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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. |
-
net10.0
- Soenneker.Atomics.ValueInts (>= 4.0.26)
- Soenneker.Extensions.Task (>= 4.0.128)
- Soenneker.Queues.Intrusive.ValueMpsc (>= 4.0.34)
NuGet packages (8)
Showing the top 5 NuGet packages that depend on Soenneker.Asyncs.Locks:
| Package | Downloads |
|---|---|
|
Soenneker.Utils.AsyncSingleton
An externally initializing singleton that uses double-check asynchronous locking, with optional async and sync disposal |
|
|
Soenneker.Dictionaries.SingletonKeys
An externally initializing singleton dictionary that uses double-check asynchronous locking, with optional async and sync disposal |
|
|
Soenneker.Blazor.Utils.Session
A Blazor utility for access-token caching and optional idle-timeout navigation |
|
|
Soenneker.Asyncs.Initializers
A lightweight, async-safe, allocation-free one-time initialization gate. Ensures a given asynchronous initialization routine runs exactly once, even under concurrent callers, with support for cancellation, safe publication, and disposal. |
|
|
Soenneker.Utils.RateLimiting.Executor
A thread-safe utility designed to manage the rate at which tasks are executed, ensuring they are not run more frequently than a specified interval. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 4.0.78 | 11,689 | 9/4/2026 |
| 4.0.77 | 7,859 | 9/3/2026 |
| 4.0.76 | 10,862 | 9/3/2026 |
| 4.0.75 | 60 | 9/3/2026 |
| 4.0.74 | 44,493 | 8/31/2026 |
| 4.0.73 | 37,099 | 8/30/2026 |
| 4.0.72 | 158 | 8/30/2026 |
| 4.0.71 | 11,251 | 8/30/2026 |
| 4.0.70 | 22,386 | 8/29/2026 |
| 4.0.68 | 268,392 | 8/8/2026 |
| 4.0.67 | 33,706 | 8/8/2026 |
| 4.0.66 | 102,440 | 7/28/2026 |
| 4.0.65 | 32,682 | 7/28/2026 |
| 4.0.64 | 138 | 7/28/2026 |
| 4.0.63 | 19,413 | 7/27/2026 |
| 4.0.62 | 113,038 | 7/19/2026 |
| 4.0.61 | 33,539 | 7/18/2026 |
| 4.0.60 | 46,832 | 7/16/2026 |
| 4.0.59 | 112 | 7/16/2026 |
| 4.0.58 | 28,423 | 7/16/2026 |
Updated NuGet packages