A high-performance, lightweight distributed lock library for SQL Server with zero external dependencies.
- 🔒 Distributed Locking: Acquire and release locks across multiple processes/applications
- 🗄️ SQL Server Backend: Uses SQL Server as the lock storage mechanism
- ⚡ Zero Dependencies: Built with raw ADO.NET for maximum performance and minimal footprint
- 🔄 Sync & Async: Full support for both synchronous and asynchronous operations
- 🎯 Universal Compatibility: Single package supports .NET Framework 4.0 through .NET 8+
- 🚀 High Performance: Optimized lock acquisition with READ COMMITTED isolation
- 🔧 Simple API: Clean, intuitive interface with manual lock release
- 📦 Lightweight: Only 49KB package size
Install the package via NuGet:
# Single package for ALL .NET versions (4.0 through 8+)
dotnet add package MDLSoft.DistributedLockOr via Package Manager Console:
# Works with any .NET version
Install-Package MDLSoft.DistributedLockvar connectionString = "Server=.;Database=MyApp;Integrated Security=true;";
var provider = new SqlServerDistributedLockProvider(connectionString);
// Ensure the locks table exists
await provider.EnsureTableExistsAsync();// Synchronous usage
using (var lockProvider = new SqlServerDistributedLockProvider(connectionString))
{
using (var distributedLock = lockProvider.TryAcquireLock("my-resource"))
{
if (distributedLock != null)
{
// Critical section - only one process can execute this
Console.WriteLine("Lock acquired successfully!");
// Do your work here...
}
else
{
Console.WriteLine("Could not acquire lock");
}
} // Lock is automatically released here
}
// Asynchronous usage
using (var lockProvider = new SqlServerDistributedLockProvider(connectionString))
{
using (var distributedLock = await lockProvider.TryAcquireLockAsync("my-resource"))
{
if (distributedLock != null)
{
// Critical section
await DoImportantWorkAsync();
}
}
}var lockProvider = new SqlServerDistributedLockProvider(connectionString);
try
{
// Acquire lock with timeout
using (var distributedLock = await lockProvider.AcquireLockAsync(
lockId: "critical-operation",
timeout: TimeSpan.FromSeconds(30)))
{
// Your critical work here
await ProcessLongRunningOperationAsync();
// Manual release (optional - automatic on dispose)
await distributedLock.ReleaseAsync();
}
}
catch (DistributedLockTimeoutException ex)
{
Console.WriteLine($"Could not acquire lock '{ex.LockId}' within the specified timeout");
}This repository includes comprehensive example applications demonstrating various usage patterns:
- Target: .NET 8 (modern applications)
- Features: Async/await, cancellation tokens, modern C# patterns
- Use Case: Web applications, services, modern desktop apps
- Target: .NET Framework 4.0 (legacy compatibility)
- Features: Synchronous operations, classic .NET patterns, Thread-based concurrency
- Use Case: Legacy applications, Windows services, older codebases
What the examples demonstrate:
- ✅ Basic lock acquisition and release patterns
- ✅ Lock timeout handling and conflict resolution
- ✅ Concurrent access simulation (multiple threads/processes)
- ✅ Proper error handling and exception management
- ✅ IDisposable pattern for automatic cleanup
- ✅ Configuration management (app.config/appsettings.json)
- ✅ Database table initialization
- ✅ Thread safety and synchronization patterns
Running the examples:
# Modern .NET example
cd MDLSoft.DistributedLock.Example
dotnet run
# .NET Framework 4.0 example (requires Visual Studio or MSBuild)
cd MDLSoft.DistributedLock.Example.Net40
# Build with Visual Studio or MSBuild, then run the .exeThe main interface for acquiring distributed locks.
TryAcquireLock(string lockId, TimeSpan? timeout = null)- Attempts to acquire a lock synchronouslyTryAcquireLockAsync(string lockId, TimeSpan? timeout = null, CancellationToken cancellationToken = default)- Attempts to acquire a lock asynchronouslyAcquireLock(string lockId, TimeSpan? timeout = null)- Acquires a lock synchronously (throws on failure)AcquireLockAsync(string lockId, TimeSpan? timeout = null, CancellationToken cancellationToken = default)- Acquires a lock asynchronously (throws on failure)
Represents an acquired distributed lock.
string LockId- The unique identifier for the lockbool IsAcquired- Whether the lock is currently acquired
Release()- Releases the lock synchronouslyReleaseAsync(CancellationToken cancellationToken = default)- Releases the lock asynchronously
SQL Server implementation of the distributed lock provider.
public SqlServerDistributedLockProvider(string connectionString, string tableName = "DistributedLocks")EnsureTableExists()- Creates the locks table if it doesn't exist (sync)EnsureTableExistsAsync(CancellationToken cancellationToken = default)- Creates the locks table if it doesn't exist (async)
The library automatically creates the following table structure:
CREATE TABLE [DistributedLocks] (
[LockId] NVARCHAR(255) NOT NULL PRIMARY KEY,
[LockToken] NVARCHAR(255) NOT NULL,
[CreatedAt] DATETIME NOT NULL DEFAULT GETUTCDATE()
);The library supports:
- System.Data.SqlClient (for .NET Framework 4.0-4.8)
- Microsoft.Data.SqlClient (for .NET Standard 2.0/.NET Core/.NET 5+)
You can specify a custom table name for the locks:
var provider = new SqlServerDistributedLockProvider(connectionString, "MyCustomLocksTable");The library includes specific exception types:
DistributedLockException- Base exception for all lock operationsDistributedLockTimeoutException- Thrown when a lock cannot be acquired within the timeout periodDistributedLockOperationException- Thrown when a lock operation fails
- Always use
usingstatements to ensure locks are properly released - Use timeouts when acquiring locks to avoid indefinite waiting
- Handle timeout exceptions gracefully in your application
- Monitor lock duration - Keep critical sections as short as possible
- Use meaningful lock IDs that clearly identify the resource being protected
- Lightweight: Zero external dependencies (except minimal framework backports)
- Fast: Raw ADO.NET operations with optimized SQL
- Efficient: Lock acquisition uses READ COMMITTED isolation with primary key constraints
- Scalable: Consider the database load when using many concurrent locks
The library is thread-safe and can be used from multiple threads simultaneously. Each lock acquisition creates a unique lock token to prevent conflicts.
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.
If you encounter any issues or have questions, please open an issue on GitHub.