NLTechnologies.LockIt 2.1.0

dotnet add package NLTechnologies.LockIt --version 2.1.0
                    
NuGet\Install-Package NLTechnologies.LockIt -Version 2.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="NLTechnologies.LockIt" Version="2.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="NLTechnologies.LockIt" Version="2.1.0" />
                    
Directory.Packages.props
<PackageReference Include="NLTechnologies.LockIt" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add NLTechnologies.LockIt --version 2.1.0
                    
#r "nuget: NLTechnologies.LockIt, 2.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package NLTechnologies.LockIt@2.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=NLTechnologies.LockIt&version=2.1.0
                    
Install as a Cake Addin
#tool nuget:?package=NLTechnologies.LockIt&version=2.1.0
                    
Install as a Cake Tool

🔒 LockIt

NuGet License CI

Lightweight, async, per-key locking for .NET. Serialize concurrent operations on the same key while allowing full parallelism across different keys.

Features

Feature Description
Per-key locking Operations on different keys run in parallel; same-key operations are serialized
Async-first Fully async/await-based with no thread blocking
Timeout & cancellation Optional TimeSpan timeout and CancellationToken on every acquisition
Try-pattern TryAcquireAsync returns a result struct instead of throwing on timeout
Automatic idle cleanup Stale locks are removed on a configurable timer to prevent unbounded growth
Long-held lock detection Warnings are logged when a lock exceeds a configurable threshold
Built-in metrics System.Diagnostics.Metrics instrumentation compatible with OpenTelemetry
Graceful disposal DisposeAsync drains in-flight locks with an optional timeout
Dependency injection One-line registration via AddLockIt()
Testable IAsyncKeyedLocker<TKey> for mocking; TimeProvider for deterministic tests

Installation

dotnet add package NLTechnologies.LockIt

Quick Start

Manual Instantiation

using NLTechnologies.LockIt; using Microsoft.Extensions.Logging;
using var loggerFactory = LoggerFactory.Create(b => b.AddConsole()); var logger = loggerFactory.CreateLogger<AsyncKeyedLocker<string>>();
await using var locker = new AsyncKeyedLocker<string>(logger);
await using (await locker.AcquireAsync("order-123")) 
{ 
    // Only one task can execute this block for "order-123" at a time. 
    // Other keys like "order-456" are NOT blocked. 
    await ProcessOrderAsync("order-123"); 
}

With Dependency Injection

Register all LockIt services in one call:

Program.cs ⇒ builder.Services.AddLockIt();

Then inject IAsyncKeyedLocker<TKey> where needed:

public class OrderService 
{ 
    private readonly IAsyncKeyedLocker<string> _locker;

    public OrderService(IAsyncKeyedLockerFactory lockerFactory)
    {
        _locker = lockerFactory.Create<string>();
    }

    public async Task HandleAsync(string orderId, CancellationToken ct)
    {
        await using (await _locker.AcquireAsync(orderId, cancellationToken: ct))
        {
            await ProcessOrderAsync(orderId);
        }
    }
}

Try-Pattern (Non-Throwing Timeout)

await using var result = await locker.TryAcquireAsync("key", TimeSpan.FromSeconds(5));

if (result.Acquired) 
{ 
    // critical section 
} 
else 
{ 
    // lock was not acquired within the timeout 
}

Configuration

Pass AsyncKeyedLockerOptions to customize behavior:

var options = new AsyncKeyedLockerOptions 
{     
    // how often cleanup runs 
    LockIdleCleanupInterval = TimeSpan.FromSeconds(60),  

    // idle time before removal 
    LockIdleCleanupThreshold = TimeSpan.FromSeconds(30),  
    
    // threshold for warnings 
    LongHeldLockThreshold  = TimeSpan.FromMinutes(1),   
    
    // max wait during disposal 
    DisposeDrainTimeout  = TimeSpan.FromSeconds(10) 
};

await using var locker = new AsyncKeyedLocker<string>(logger, options);

Options available:

Option Default Description
LockIdleCleanupInterval 60 s How often the cleanup timer runs
LockIdleCleanupThreshold 10 s Idle time before a lock is eligible for removal
LongHeldLockLoggingInterval 10 s How often long-held locks are checked
LongHeldLockThreshold 30 s Threshold for logging a long-held warning
DisposeDrainTimeout null (∞) Max wait for in-flight locks during disposal

API Reference

IAsyncKeyedLocker<TKey>

Method Description
AcquireAsync(key, timeout?, ct) Acquires the lock. Returns an IAsyncDisposable lease.
TryAcquireAsync(key, timeout, ct) Non-throwing variant. Returns TryAcquireResult.
GetQueueDepth(key) Number of tasks holding or waiting on the lock for the given key.

IAsyncKeyedLockerFactory

Method Description
Create<TKey>(options?) Creates a new independent IAsyncKeyedLocker<TKey> instance.

ServiceCollectionExtensions

Method Description
AddLockIt() Registers IAsyncKeyedLockerFactory, LockItMetrics, and TimeProvider as singletons.

Metrics (OpenTelemetry)

LockIt exposes metrics under the meter name NLTechnologies.LockIt:

Instrument Type Unit Description
lockit.locks.acquired Counter locks Total successful acquisitions
lockit.locks.released Counter locks Total releases
lockit.locks.timed_out Counter locks Total acquisition timeouts
lockit.locks.active UpDownCounter locks Currently held locks
lockit.locks.contention_time Histogram ms Time spent waiting to acquire
lockit.cleanup.removed Counter locks Idle locks removed by cleanup

Subscribe in your OpenTelemetry configuration:

builder.Services.AddOpenTelemetry().WithMetrics(m => m.AddMeter(LockItMetrics.MeterName));

Project Structure

LockIt/ 
├── src/ │   
         └── NLTechnologies.LockIt/ │       
                                    ├── AsyncKeyedLocker.cs │       
                                    ├── AsyncKeyedLockerFactory.cs │       
                                    ├── AsyncKeyedLockerOptions.cs │       
                                    ├── IAsyncKeyedLocker.cs │       
                                    ├── IAsyncKeyedLockerFactory.cs │       
                                    ├── LockItMetrics.cs │       
                                    ├── ServiceCollectionExtensions.cs │       
                                    └── TryAcquireResult.cs 
├── tests/ │
           └── NLTechnologies.LockIt.Tests/ 
├── .github/workflows/ci.yml 
├── .editorconfig 
├── CHANGELOG.md 
├── CONTRIBUTING.md 
├── Directory.Build.props 
├── LICENSE 
├── NLTechnologies.LockIt.slnx 
└── README.md

Contributing

Contributions are welcome! See CONTRIBUTING.md for guidelines.

License

Licensed under the Apache License 2.0.

Copyright © 2026 Noctua Lumen Technologies.

Product Compatible and additional computed target framework versions.
.NET 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 is compatible.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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
2.1.0 115 4/14/2026
2.0.0 115 4/13/2026