.NET

Distributed Locking in .NET: Coordinating Work Across Multiple Instances

Learn how to implement distributed locking patterns in .NET applications to coordinate work across multiple instances safely.

I
Isaiah Clifford Opoku
Dec 15, 20242 min read
#dotnet#distributed-systems#concurrency
2 min
reading time
Distributed Locking in .NET: Coordinating Work Across Multiple Instances

When building scalable .NET applications that run across multiple instances, you'll often need to coordinate work to ensure that certain operations are performed by only one instance at a time. This is where distributed locking comes into play.

What is Distributed Locking?

Distributed locking is a mechanism that allows you to coordinate access to shared resources across multiple application instances. Unlike traditional thread-level locking (using lock statements or Mutex), distributed locks work across different processes and machines.

Common Use Cases

Here are some scenarios where distributed locking is essential:

  • Scheduled Jobs: Ensuring only one instance processes a scheduled task
  • Resource Processing: Preventing multiple instances from processing the same queue item
  • Configuration Updates: Coordinating configuration changes across instances
  • Database Migrations: Ensuring migrations run only once across deployments

Implementation with Redis

Let's implement a distributed lock using Redis and the StackExchange.Redis library:

csharp
1public class RedisDistributedLock : IDistributedLock 2{ 3 private readonly IDatabase _database; 4 private readonly string _lockKey; 5 private readonly string _lockValue; 6 private readonly TimeSpan _expiry; 7 8 public RedisDistributedLock( 9 IDatabase database, 10 string lockKey, 11 TimeSpan expiry) 12 { 13 _database = database; 14 _lockKey = lockKey; 15 _lockValue = Guid.NewGuid().ToString(); 16 _expiry = expiry; 17 } 18 19 public async Task<bool> TryAcquireAsync() 20 { 21 return await _database.StringSetAsync( 22 _lockKey, 23 _lockValue, 24 _expiry, 25 When.NotExists); 26 } 27 28 public async Task ReleaseAsync() 29 { 30 const string script = @" 31 if redis.call('get', KEYS[1]) == ARGV[1] then 32 return redis.call('del', KEYS[1]) 33 else 34 return 0 35 end"; 36 37 await _database.ScriptEvaluateAsync( 38 script, 39 new RedisKey[] { _lockKey }, 40 new RedisValue[] { _lockValue }); 41 } 42}

Usage Example

Here's how to use the distributed lock in a background service:

csharp
1public class OrderProcessingService : BackgroundService 2{ 3 private readonly IDistributedLockFactory _lockFactory; 4 private readonly IOrderProcessor _orderProcessor; 5 6 protected override async Task ExecuteAsync(CancellationToken stoppingToken) 7 { 8 while (!stoppingToken.IsCancellationRequested) 9 { 10 using var distributedLock = _lockFactory.CreateLock( 11 "order-processing-lock", 12 TimeSpan.FromMinutes(5)); 13 14 if (await distributedLock.TryAcquireAsync()) 15 { 16 try 17 { 18 await _orderProcessor.ProcessPendingOrdersAsync(); 19 } 20 finally 21 { 22 await distributedLock.ReleaseAsync(); 23 } 24 } 25 26 await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken); 27 } 28 } 29}

Best Practices

  1. Always Set Expiration: Locks should have a reasonable expiration time to prevent deadlocks
  2. Use Unique Values: Each lock acquisition should use a unique identifier
  3. Proper Cleanup: Always release locks in a finally block or using statement
  4. Handle Failures: Plan for scenarios where lock acquisition fails
  5. Monitor Performance: Track lock contention and acquisition times

Alternative Solutions

While Redis is popular for distributed locking, consider these alternatives:

  • Database-based locks: Using database transactions and row-level locking
  • Cloud services: Azure Service Bus, AWS SQS with message visibility timeouts
  • Consul: HashiCorp Consul's session-based locking
  • ZooKeeper: Apache ZooKeeper for coordination services

Conclusion

Distributed locking is a crucial pattern for building reliable multi-instance .NET applications. By implementing proper distributed locking mechanisms, you can ensure data consistency and prevent race conditions across your distributed system.

Remember to always consider the trade-offs between performance, complexity, and reliability when choosing your distributed locking strategy.

Related Technologies

Technologies and tools featured in this article

#

dotnet

#

distributed-systems

#

concurrency

#

redis

4
Technologies
.NET
Category
2m
Read Time

Continue Reading

Discover more insights and technical articles from our collection

Back to all posts

Found this helpful?

I'm posting .NET and software engineering content on multiple Social Media platforms.

If you enjoyed this article and want to see more content like this, consider subscribing to my newsletter or following me on social media for the latest updates.