DistributedLock is a .NET library that provides robust and easy-to-use distributed mutexes, reader-writer locks, and semaphores based on a variety of underlying technologies.
With DistributedLock, synchronizing access to a region of code across multiple applications/machines is as simple as:
await using (await myDistributedLock.AcquireAsync())
{
// I hold the lock here
}
DistributedLock contains implementations based on various technologies; you can install implementation packages individually or just install the DistributedLock NuGet package , a "meta" package which includes all implementations as dependencies. Note that each package is versioned independently according to SemVer.
WaitHandles)Click on the name of any of the above packages to see the documentation specific to that implementation, or read on for general documentation that applies to all implementations.
The DistributedLock.Core package contains common code and abstractions and is referenced by all implementations.
While all implementations support locks, the other primitives are only supported by some implementations. See the implementation-specific documentation pages for details.
Because distributed locks (and other distributed synchronization primitives) are not isolated to a single process, their identity is based on their name which is provided through the constructor. Different underlying technologies have different restrictions on name format; however, DistributedLock largely allows you to ignore these by escaping/hashing names that would otherwise be invalid.
All synchronization primitives support the same basic access pattern. The Acquire method returns a "handle" object that represents holding the lock. When the handle is disposed, the lock is released:
var myDistributedLock = new SqlDistributedLock(name, connectionString); // e. g. if we are using SQL Server
using (myDistributedLock.Acquire())
{
// we hold the lock here
} // implicit Dispose() call from using block releases it here
While Acquire will block until the lock is available, there is also a TryAcquire variant which returns null if the lock could not be acquired (due to being held elsewhere):
using (var handle = myDistributedLock.TryAcquire())
{
if (handle != null)
{
// we acquired the lock :-)
}
else
{
// someone else has it :-(
}
}
async versions of both of these methods are also supported. These are preferred when you are writing async code since they will not consume a thread while waiting for the lock. If you are using C#8 or higher, you can also dispose of handles asynchronously:
await using (await myDistributedLock.AcquireAsync()) { ... }
Additionally, all of these methods support an optional timeout parameter. timeout determines how long Acquire will wait before failing with a TimeoutException and how long TryAcquire will wait before returning null. The default timeout for Acquire is Timeout.InfiniteTimeSpan while for TryAcquire the default timeout is TimeSpan.Zero.
Finally, the methods take an optional CancellationToken parameter, which allows for the acquire operation to be interrupted via cancellation. Note that this won't cancel the hold on the lock once the acquire succeeds.
For applications that use dependency injection, DistributedLock's providers make it easy to separate out the specification of a lock's (or other primitive's) name from its other settings (such as a database connection string). For example in an ASP.NET Core app you might do:
// in your Startup.cs:
services.AddSingleton<IDistributedLockProvider>(_ => new PostgresDistributedSynchronizationProvider(myConnectionString));
services.AddTransient<SomeService>();
// in SomeService.cs
public class SomeService
{
private readonly IDistributedLockProvider _synchronizationProvider;
public SomeService(IDistributedLockProvider synchronizationProvider)
{
this._synchronizationProvider = synchronizationProvider;
}
public void InitializeUserAccount(int id)
{
// use the provider to construct a lock
var @lock = this._synchronizationProvider.CreateLock($"UserAccount{id}");
using (@lock.Acquire())
{
// do stuff
}
// ALTERNATIVELY, for common use-cases extension methods allow this to be done with a single call
using (this._synchronizationProvider.AcquireLock($"UserAccount{id}"))
{
// do stuff
}
}
}
Contributions are welcome! If you are interested in contributing towards a new or existing issue, please let me know via comments on the issue so that I can help you get started and avoid wasted effort on your part.
Setup steps for working with the repository locally are documented here.
SqlDistributedSemaphore deadlock detection (#264, DistributedLock.SqlServer 1.0.7)FileDistributedLock (DistributedLock.FileSystem 1.0.3)DbDataSource which is helpful for apps using NpgsqlMultiHostDataSource. Thanks davidngjy for implementing! (#153, DistributedLock.Postgres 1.2.0)pg_advisory_xact_lock which is helpful when using PgBouncer (#168, DistributedLock.Postgres 1.1.0)net461 support (net462 remains supported). Thanks @Bartleby2718 for implementing!UnobservedTaskExceptions thrown by the library (#192, DistributedLock.Core 1.0.6)FileDistributedLock on Linux/.NET 8 (#195, DistributedLock.FileSystem 1.0.2)HandleLostToken for relational database locks (#133, DistributedLock.Core 1.0.5, DistributedLock.MySql 1.0.1, DistributedLock.Oracle 1.0.1, DistributedLock.Postgres 1.0.3, DistributedLock.SqlServer 1.0.2). Thanks @OskarKlintrot for testing!WaitHandles (#120, DistributedLock.WaitHandles 1.0.1)UnauthorizedAccessExceptions (#106 & #109, DistributedLock.FileSystem 1.0.1)HandleLostToken would hang when accessed on a SqlServer or Postgres lock handle that used keepalive (#85, DistributedLock.Core 1.0.1)WithKeyPrefix (#66, DistributedLock.Redis 1.0.1). Thanks @skomis-mm for contributing!IAsyncDisposable in addition to IDisposable #20, BREAKING CHANGE)IDistributedLock) for all synchronization primitives (#10)HandleLostToken API for tracking if a lock's underlying connection dies (#6, BREAKING CHANGE)GetSafeName API in favor of safe naming by default (BREAKING CHANGE)DbConnection and DbTransaction constructors form SqlDistributedLock, leaving the constructors that take IDbConnection/IDbTransaction (#35, BREAKING CHANGE)Task<IDisposable> to instead return ValueTask, making it so that using (@lock.AcquireAsync()) { ... } without an await` no longer compiles (#34, BREAKING CHANGE)UpgradeableLockHandle.UpgradeToWriteLock to return void (#33, BREAKING CHANGE)DeadlockException) rather than the generic InvalidOperationException when a deadlock is detected (#11)Overall
Maintained
Code review
NuGet
Latest release
Releases this year
197 followers · starred Aug 2021
23 followers · starred Jan 2023
45 followers · starred Sep 2025
31 followers · starred Jan 2020
DistributedLock is a .NET library that provides robust and easy-to-use distributed mutexes, reader-writer locks, and semaphores based on a variety of underlying technologies.
With DistributedLock, synchronizing access to a region of code across multiple applications/machines is as simple as:
await using (await myDistributedLock.AcquireAsync())
{
// I hold the lock here
}
DistributedLock contains implementations based on various technologies; you can install implementation packages individually or just install the DistributedLock NuGet package , a "meta" package which includes all implementations as dependencies. Note that each package is versioned independently according to SemVer.
WaitHandles)Click on the name of any of the above packages to see the documentation specific to that implementation, or read on for general documentation that applies to all implementations.
The DistributedLock.Core package contains common code and abstractions and is referenced by all implementations.
While all implementations support locks, the other primitives are only supported by some implementations. See the implementation-specific documentation pages for details.
Because distributed locks (and other distributed synchronization primitives) are not isolated to a single process, their identity is based on their name which is provided through the constructor. Different underlying technologies have different restrictions on name format; however, DistributedLock largely allows you to ignore these by escaping/hashing names that would otherwise be invalid.
All synchronization primitives support the same basic access pattern. The Acquire method returns a "handle" object that represents holding the lock. When the handle is disposed, the lock is released:
var myDistributedLock = new SqlDistributedLock(name, connectionString); // e. g. if we are using SQL Server
using (myDistributedLock.Acquire())
{
// we hold the lock here
} // implicit Dispose() call from using block releases it here
While Acquire will block until the lock is available, there is also a TryAcquire variant which returns null if the lock could not be acquired (due to being held elsewhere):
using (var handle = myDistributedLock.TryAcquire())
{
if (handle != null)
{
// we acquired the lock :-)
}
else
{
// someone else has it :-(
}
}
async versions of both of these methods are also supported. These are preferred when you are writing async code since they will not consume a thread while waiting for the lock. If you are using C#8 or higher, you can also dispose of handles asynchronously:
await using (await myDistributedLock.AcquireAsync()) { ... }
Additionally, all of these methods support an optional timeout parameter. timeout determines how long Acquire will wait before failing with a TimeoutException and how long TryAcquire will wait before returning null. The default timeout for Acquire is Timeout.InfiniteTimeSpan while for TryAcquire the default timeout is TimeSpan.Zero.
Finally, the methods take an optional CancellationToken parameter, which allows for the acquire operation to be interrupted via cancellation. Note that this won't cancel the hold on the lock once the acquire succeeds.
For applications that use dependency injection, DistributedLock's providers make it easy to separate out the specification of a lock's (or other primitive's) name from its other settings (such as a database connection string). For example in an ASP.NET Core app you might do:
// in your Startup.cs:
services.AddSingleton<IDistributedLockProvider>(_ => new PostgresDistributedSynchronizationProvider(myConnectionString));
services.AddTransient<SomeService>();
// in SomeService.cs
public class SomeService
{
private readonly IDistributedLockProvider _synchronizationProvider;
public SomeService(IDistributedLockProvider synchronizationProvider)
{
this._synchronizationProvider = synchronizationProvider;
}
public void InitializeUserAccount(int id)
{
// use the provider to construct a lock
var @lock = this._synchronizationProvider.CreateLock($"UserAccount{id}");
using (@lock.Acquire())
{
// do stuff
}
// ALTERNATIVELY, for common use-cases extension methods allow this to be done with a single call
using (this._synchronizationProvider.AcquireLock($"UserAccount{id}"))
{
// do stuff
}
}
}
Contributions are welcome! If you are interested in contributing towards a new or existing issue, please let me know via comments on the issue so that I can help you get started and avoid wasted effort on your part.
Setup steps for working with the repository locally are documented here.
SqlDistributedSemaphore deadlock detection (#264, DistributedLock.SqlServer 1.0.7)FileDistributedLock (DistributedLock.FileSystem 1.0.3)DbDataSource which is helpful for apps using NpgsqlMultiHostDataSource. Thanks davidngjy for implementing! (#153, DistributedLock.Postgres 1.2.0)pg_advisory_xact_lock which is helpful when using PgBouncer (#168, DistributedLock.Postgres 1.1.0)net461 support (net462 remains supported). Thanks @Bartleby2718 for implementing!UnobservedTaskExceptions thrown by the library (#192, DistributedLock.Core 1.0.6)FileDistributedLock on Linux/.NET 8 (#195, DistributedLock.FileSystem 1.0.2)HandleLostToken for relational database locks (#133, DistributedLock.Core 1.0.5, DistributedLock.MySql 1.0.1, DistributedLock.Oracle 1.0.1, DistributedLock.Postgres 1.0.3, DistributedLock.SqlServer 1.0.2). Thanks @OskarKlintrot for testing!WaitHandles (#120, DistributedLock.WaitHandles 1.0.1)UnauthorizedAccessExceptions (#106 & #109, DistributedLock.FileSystem 1.0.1)HandleLostToken would hang when accessed on a SqlServer or Postgres lock handle that used keepalive (#85, DistributedLock.Core 1.0.1)WithKeyPrefix (#66, DistributedLock.Redis 1.0.1). Thanks @skomis-mm for contributing!IAsyncDisposable in addition to IDisposable #20, BREAKING CHANGE)IDistributedLock) for all synchronization primitives (#10)HandleLostToken API for tracking if a lock's underlying connection dies (#6, BREAKING CHANGE)GetSafeName API in favor of safe naming by default (BREAKING CHANGE)DbConnection and DbTransaction constructors form SqlDistributedLock, leaving the constructors that take IDbConnection/IDbTransaction (#35, BREAKING CHANGE)Task<IDisposable> to instead return ValueTask, making it so that using (@lock.AcquireAsync()) { ... } without an await` no longer compiles (#34, BREAKING CHANGE)UpgradeableLockHandle.UpgradeToWriteLock to return void (#33, BREAKING CHANGE)DeadlockException) rather than the generic InvalidOperationException when a deadlock is detected (#11)Overall
Maintained
Code review
NuGet
Latest release
Releases this year
197 followers · starred Aug 2021
23 followers · starred Jan 2023
45 followers · starred Sep 2025
31 followers · starred Jan 2020