AnoiKeyedLock 1.0.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package AnoiKeyedLock --version 1.0.0
                    
NuGet\Install-Package AnoiKeyedLock -Version 1.0.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="AnoiKeyedLock" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="AnoiKeyedLock" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="AnoiKeyedLock" />
                    
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 AnoiKeyedLock --version 1.0.0
                    
#r "nuget: AnoiKeyedLock, 1.0.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 AnoiKeyedLock@1.0.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=AnoiKeyedLock&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=AnoiKeyedLock&version=1.0.0
                    
Install as a Cake Tool

AnoiKeyedLock

A high-performance, low-allocation keyed lock implementation for .NET that ensures exclusive access per string key with automatic resource cleanup.

Features

✅ High Performance: Lock-free reference counting with minimal contention
✅ Low Allocations: Struct-based releaser avoids heap allocations
✅ Automatic Cleanup: Keys and semaphores are automatically removed when no longer needed
✅ Thread-Safe: Fully thread-safe for concurrent access
✅ Flexible API: Support for sync/async, timeouts, and cancellation tokens
✅ String Keys: Optimized for string-based locking with optional case-insensitive comparison
✅ .NET Standard 2.1: Compatible with .NET Core 3.0+, .NET 5+, and modern platforms

Installation

Simply include the KeyedLock.cs file in your project, or build and reference the assembly.

Quick Start

using AnoiKeyedLock;

var keyedLock = new KeyedLock();

// Basic usage
using (var releaser = keyedLock.Lock("myKey"))
{
    // Only one thread can execute this block for "myKey" at a time
    DoWork();
}

// Async usage
using (var releaser = await keyedLock.LockAsync("myKey"))
{
    await DoWorkAsync();
}

// With timeout
if (keyedLock.TryLock("myKey", TimeSpan.FromSeconds(5), out var releaser))
{
    using (releaser)
    {
        DoWork();
    }
}

API Reference

Constructor

// Default (ordinal) string comparison
var keyedLock = new KeyedLock();

// Case-insensitive comparison
var keyedLock = new KeyedLock(StringComparer.OrdinalIgnoreCase);

Synchronous Methods

Lock(string key)

Acquires a lock for the specified key. Blocks indefinitely until the lock is acquired.

KeyedLockReleaser Lock(string key)
TryLock(string key, TimeSpan timeout, out KeyedLockReleaser releaser)

Tries to acquire a lock within the specified timeout.

bool TryLock(string key, TimeSpan timeout, out KeyedLockReleaser releaser)
bool TryLock(string key, int millisecondsTimeout, out KeyedLockReleaser releaser)
TryLock(string key, CancellationToken cancellationToken, out KeyedLockReleaser releaser)

Tries to acquire a lock with cancellation support.

bool TryLock(string key, CancellationToken cancellationToken, out KeyedLockReleaser releaser)

Asynchronous Methods

LockAsync(string key, CancellationToken cancellationToken = default)

Asynchronously acquires a lock for the specified key.

Task<KeyedLockReleaser> LockAsync(string key, CancellationToken cancellationToken = default)
TryLockAsync(string key, TimeSpan timeout, CancellationToken cancellationToken = default)

Tries to asynchronously acquire a lock within the specified timeout.

Task<(bool success, KeyedLockReleaser releaser)> TryLockAsync(
    string key, 
    TimeSpan timeout, 
    CancellationToken cancellationToken = default)

Task<(bool success, KeyedLockReleaser releaser)> TryLockAsync(
    string key, 
    int millisecondsTimeout, 
    CancellationToken cancellationToken = default)

Properties

Count

Gets the current number of keys being tracked.

int Count { get; }

How It Works

Architecture

  1. Concurrent Dictionary: Stores semaphores keyed by string keys
  2. Reference Counting: Tracks how many threads are waiting for or holding each lock
  3. Automatic Cleanup: When the reference count reaches zero, the semaphore is disposed and removed
  4. Struct-based Releaser: The KeyedLockReleaser is a struct to avoid heap allocations

Thread Safety

  • Multiple threads can acquire locks on different keys simultaneously without blocking
  • Multiple threads waiting on the same key will queue in FIFO order
  • Reference counting uses lock-free atomic operations (Interlocked)
  • Dictionary operations use ConcurrentDictionary for thread-safe access

Performance Characteristics

Operation Complexity Allocations
Lock acquisition O(1) amortized ~0 (struct releaser)
Lock release O(1) amortized 0
Key cleanup O(1) amortized 0

Use Cases

Prevent Duplicate API Calls

public class ApiClient
{
    private readonly KeyedLock _lock = new KeyedLock();

    public async Task<User> GetUserAsync(string userId)
    {
        using (var releaser = await _lock.LockAsync(userId))
        {
            return await _httpClient.GetFromJsonAsync<User>($"/users/{userId}");
        }
    }
}

File Processing Coordination

public class FileProcessor
{
    private readonly KeyedLock _lock = new KeyedLock();

    public async Task ProcessFileAsync(string filePath)
    {
        using (var releaser = await _lock.LockAsync(filePath))
        {
            // Ensure only one thread processes this file at a time
            await ProcessFileInternalAsync(filePath);
        }
    }
}

Database Connection Management

public class ConnectionManager
{
    private readonly KeyedLock _lock = new KeyedLock();

    public async Task<T> ExecuteAsync<T>(string connectionString, Func<DbConnection, Task<T>> operation)
    {
        using (var releaser = await _lock.LockAsync(connectionString))
        {
            using (var connection = new SqlConnection(connectionString))
            {
                await connection.OpenAsync();
                return await operation(connection);
            }
        }
    }
}

Rate Limiting

public class RateLimiter
{
    private readonly KeyedLock _lock = new KeyedLock();

    public async Task<bool> TryExecuteAsync(string clientId, Func<Task> action, TimeSpan minInterval)
    {
        var result = await _lock.TryLockAsync(clientId, TimeSpan.FromMilliseconds(1));
        if (result.success)
        {
            using (result.releaser)
            {
                await action();
                await Task.Delay(minInterval); // Enforce minimum interval
                return true;
            }
        }
        return false;
    }
}

Best Practices

  1. ✅ Always use using statements to ensure locks are released
  2. ✅ Keep critical sections short to minimize contention
  3. ✅ Use async methods for I/O operations to avoid blocking threads
  4. ✅ Keys cannot be null or whitespace - validation is performed automatically
  5. ✅ Handle timeouts gracefully - always have a fallback strategy
  6. ❌ Avoid nested locks on the same key (not reentrant)
  7. ❌ Don't hold locks across long-running operations without timeouts

Requirements

  • .NET Standard 2.1 or higher
  • C# 7.3 or higher

Compatible With

  • .NET Core 3.0+
  • .NET 5, 6, 7, 8, 9+
  • Xamarin (iOS 12.16+, Android 10.0+)
  • Unity 2021.2+

Examples

See Examples.cs for comprehensive usage examples including:

  • Basic synchronous and asynchronous locking
  • Timeout handling
  • Cancellation support
  • Real-world scenarios (file processing, API calls, etc.)
  • Performance testing

License

[Your License Here]

Contributing

Contributions are welcome! Please feel free to submit pull requests or open issues.

Product 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 was computed.  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 was computed.  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 netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos 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
1.1.0 178 1/24/2026
1.0.0 148 1/20/2026

Initial release with full keyed locking functionality, async/await support, and dependency injection integration.