Dapr Distributed Lock .NET SDK
Overview of the Dapr Distributed Lock building block for .NET
With the Dapr Distributed Lock package, you can create and remove locks on resources to manage exclusivity across
your distributed applications. The DaprDistributedLockClient provides a dedicated client for interacting with Dapr’s
distributed lock API, supporting lock acquisition with automatic release on disposal, configurable timeouts, and
cancellation.
Package guidance
While this capability is implemented in both the Dapr.Client and Dapr.DistributedLock packages, the approach differs
slightly between them. It’s recommended that new
implementations use the Dapr.DistributedLock package.Core concepts
Next steps
1 - How to: Create and use Dapr Distributed Lock in the .NET SDK
Learn how to create and use the Dapr Distributed Lock client using the .NET SDK
Prerequisites
Installation
.NET framework support
Be advised that .NET 8 and .NET 9 reach end of support in November 2026. Support for both will be dropped
in Dapr v1.20; only .NET 10 and .NET 11 will be supported through the end of 2027.To get started with the Dapr Distributed lock .NET SDK client, install the Dapr.Distributed Lock package from NuGet:
dotnet add package Dapr.DistributedLock
The examples below target SDK 1.19.0 or later and require a configured distributed lock component,
such as a Redis lock store named lockstore. Import Dapr.DistributedLock and
Dapr.DistributedLock.Extensions. This API is experimental (DAPR_DISTRIBUTEDLOCK).
A DaprDistributedLockClient maintains access to networking resources in the form of TCP sockets used to communicate with the Dapr sidecar.
Dependency Injection
The AddDaprDistributedLock() method will register the Dapr client ASP.NET Core dependency injection and is the recommended approach
for using this package. This method accepts an optional options delegate for configuring the DaprDistributedLockClient and a
ServiceLifetime argument, allowing you to specify a different lifetime for the registered services instead of the default Scoped
value.
The following example assumes all default values are acceptable and is sufficient to register the DaprDistributedLockClient:
services.AddDaprDistributedLock();
The optional configuration delegate is used to configure the DaprDistributedLockClient by specifying options on the
DaprDistributedLockBuilder as in the following example:
services.AddSingleton<DefaultOptionsProvider>();
services.AddDaprDistributedLock((serviceProvider, clientBuilder) => {
//Inject a service to source a value from
var optionsProvider = serviceProvider.GetRequiredService<DefaultOptionsProvider>();
var standardTimeout = optionsProvider.GetStandardTimeout();
//Configure the value on the client builder
clientBuilder.UseTimeout(standardTimeout);
});
Manual Instantiation
Rather than using dependency injection, a DaprDistributedLockClient can also be built using the static client builder.
For best performance, create a single long-lived instance of DaprDistributedLockClient and provide access to that shared instance throughout
your application. DaprDistributedLockClient instances are thread-safe and intended to be shared.
Avoid creating a DaprDistributedLockClient per-operation.
A DaprDistributedLockClient can be configured by invoking methods on the DaprDistributedLockBuilder class before calling .Build()
to create the client. The settings for each DaprDistributedLockClient are separate and cannot be changed after calling .Build().
var daprDistributedLockClient = new DaprDistributedLockBuilder()
.UseGrpcEndpoint("http://localhost:50001")
.Build();
See the .NET documentation here for more information about the options available
when configuring the Dapr Distributed Lock client via the builder.
Try it out
Put the Dapr Distributed Lock .NET SDK to the test. Walk through the samples to see Dapr in action:
| SDK Samples | Description |
|---|
| SDK samples | Clone the SDK repo to try out some examples and get started. |
Building Blocks
This part of the .NET SDK allows you to interface with the Distributed Lock API to place and remove locks for managing
resource exclusivity across your distributed applications.
Acquire and automatically release a lock
TryLockAsync makes one acquisition attempt. It returns a LockResponse when acquired and null when
the resource could not be locked. It does not wait until another owner releases the lock.
var owner = Guid.NewGuid().ToString("N");
await using var resourceLock = await daprDistributedLockClient.TryLockAsync(
"lockstore", "orders/42", owner, expiryInSeconds: 60,
cancellationToken: cancellationToken);
if (resourceLock is null)
{
Console.WriteLine("The resource is already locked; work was not started.");
}
else
{
await ProcessOrderAsync(cancellationToken);
}
LockResponse contains StoreName, ResourceId, and LockOwner and implements IAsyncDisposable.
At the end of the scope, disposal attempts to unlock with that identity. It does not expose the unlock
status to the caller. Use explicit unlock when you need to inspect the result, rather than assuming
disposal confirms successful release.
Choose an owner unique to the acquisition attempt, not just a shared app ID. The expiry must be a positive
number of seconds. Ensure protected work finishes before expiry; this client does not renew leases or
provide fencing tokens. Cancellation of the caller does not extend a lease or undo completed work.
Explicit unlock and status
Pass the same component, resource, and owner values to TryUnlockAsync:
using Dapr.DistributedLock.Models;
var result = await daprDistributedLockClient.TryUnlockAsync(
"lockstore", "orders/42", owner, cancellationToken);
if (result.Status != LockStatus.Success)
{
throw new InvalidOperationException($"Unlock did not succeed: {result.Status}");
}
LockStatus | Meaning |
|---|
Success | The lock was released. |
LockDoesNotExist | The lock no longer exists, for example because it expired. |
LockBelongsToOthers | The supplied owner does not own this lock. |
InternalError | The runtime/component reported an unlock error. |
Invalid arguments raise standard argument exceptions. gRPC failures are wrapped in DaprException;
inspect InnerException. A normal acquisition conflict is null, not a transport exception.
Migration from DaprClient
The older DaprClient.Lock returns TryLockResponse with a Success property, whereas
DaprDistributedLockClient.TryLockAsync returns a nullable disposable handle. Likewise, use
TryUnlockAsync rather than DaprClient.Unlock and inspect the dedicated client’s LockStatus.
The two response models are not interchangeable.
2 - DaprDistributedLockClient usage
Essential tips and advice for using DaprDistributedLockClient
Lifetime management
A DaprDistributedLockClient is a version of the Dapr client that is dedicated to interacting with Dapr’s distributed
lock API. It can be registered alongside a DaprClient and other Dapr clients without issue.
It maintains access to networking resources in the form of TCP sockets used to communicate with the Dapr sidecar runtime.
For best performance, it is recommended that you utilize the dependency injection container mechanisms provided with the
Dapr.DistributedLock package to provide easy access to an injected instance throughout your application. These injected
instances are thread-safe and intended to be used across different types within your application. Registration via
dependency injection can utilize values from an IConfiguration or other injected services in a way that’s impractical
when creating the client from scratch in each of your classes.
If you manually create a DaprDistributedLockClient, use DaprDistributedLockBuilder.
Avoid creating a DaprDistributedLockClient for each operation. See
acquisition, release, and status handling for the supported operations.
Configuring a DaprDistributedLockClient via DaprDistributedLockBuilder
A DaprDistributedLockClient can be configured by invoking methods on the DaprDistributedLockBuilder class before calling
.Build() to create the client itself. The settings for each DaprDistributedLockClient are separate and cannot be changed
after calling .Build().
var daprDistributedLockClient = new DaprDistributedLockBuilder()
.UseDaprApiToken("abc123") // Optionally specify the API token used to authenticate to other Dapr sidecars
.Build();
The DaprDistributedLockBuilder contains settings for:
- The HTTP endpoint of the Dapr sidecar
- The gRPC endpoint of the Dapr sidecar
- The
JsonSerializerOptions object used to configure JSON serialization - The
GrpcChannelOptions object used to configure gRPC - The API token used to authenticate requests to the sidecar
- The factory method used to create the
HttpClient instance used by the SDK - The timeout used for the
HttpClient instance when making requests to the sidecar
The SDK will read the following environment variables to configure the default values:
DAPR_HTTP_ENDPOINT: used to find the HTTP endpoint of the Dapr sidecar, example: https://dapr-api.mycompany.comDAPR_GRPC_ENDPOINT: used to find the gRPC endpoint of the Dapr sidecar, example: https://dapr-grpc-api.mycompany.comDAPR_HTTP_PORT: if DAPR_HTTP_ENDPOINT is not set, this is used to find the HTTP local endpoint of the Dapr sidecarDAPR_GRPC_PORT: if DAPR_GRPC_ENDPOINT is not set, this is used to find the gRPC local endpoint of the Dapr sidecarDAPR_API_TOKEN: used to set the API token
Configuring gRPC channel options
Dapr’s use of CancellationToken for cancellation relies on the configuration of the gRPC channel options. If you need
to configure these options yourself, make sure to enable the ThrowOperationCanceledOnCancellation setting.
var daprDistributedLockClient = new DaprDistributedLockBuilder()
.UseGrpcChannelOptions(new GrpcChannelOptions { ThrowOperationCanceledOnCancellation = true })
.Build();
Using cancellation with DaprDistributedLockClient
The APIs on DaprDistributedLockClient perform asynchronous operations and accept an optional CancellationToken parameter. This
follows a standard .NET practice for cancellable operations. Note that when cancellation occurs, there is no guarantee that
the remote endpoint stops processing the request, only that the client has stopped waiting for completion.
Cancellation can surface as OperationCanceledException or a DaprException wrapping a gRPC cancellation,
depending on the channel configuration. Inspect the inner exception rather than treating cancellation as a lock conflict.
Configuring DaprDistributedLockClient via dependency injection
Using the built-in extension methods for registering the DaprDistributedLockClient in a dependency injection container can
provide the benefit of registering the long-lived service a single time, centralize complex configuration and improve
performance by ensuring similarly long-lived resources are re-purposed when possible (e.g. HttpClient instances).
There are three overloads available to give the developer the greatest flexibility in configuring the client for their
scenario. Each of these will register the IHttpClientFactory on your behalf if not already registered, and configure
the DaprDistributedLockBuilder to use it when creating the HttpClient instance in order to re-use the same instance as
much as possible and avoid socket exhaustion and other issues.
In the first approach, there’s no configuration done by the developer and the DaprDistributedLockClient is configured with the
default settings.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDaprDistributedLock(); //Registers the `DaprDistributedLockClient` to be injected as needed
var app = builder.Build();
Sometimes the developer will need to configure the created client using the various configuration options detailed
above. This is done through an overload that passes in the DaprDistributedLockBuilder and exposes methods for configuring
the necessary options.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDaprDistributedLock((_, daprDistributedLockBuilder) => {
//Set the API token
daprDistributedLockBuilder.UseDaprApiToken("abc123");
//Specify a non-standard HTTP endpoint
daprDistributedLockBuilder.UseHttpEndpoint("http://dapr.my-company.com");
});
var app = builder.Build();
Finally, it’s possible that the developer may need to retrieve information from another service in order to populate
these configuration values. That value may be provided from a DaprClient instance, a vendor-specific SDK or some
local service, but as long as it’s also registered in DI, it can be injected into this configuration operation via the
last overload:
var builder = WebApplication.CreateBuilder(args);
//Register a fictional service that retrieves secrets from somewhere
builder.Services.AddSingleton<SecretService>();
builder.Services.AddDaprDistributedLock((serviceProvider, daprDistributedLockBuilder) => {
//Retrieve an instance of the `SecretService` from the service provider
var secretService = serviceProvider.GetRequiredService<SecretService>();
var daprApiToken = secretService.GetSecret("DaprApiToken").Value;
//Configure the `DaprDistributedLockBuilder`
daprDistributedLockBuilder.UseDaprApiToken(daprApiToken);
});
var app = builder.Build();