Coordinating Distributed Operations with Hazelcast HMap and AsyncContext in .NET
In modern microservices and distributed systems, ensuring coordinated access to shared recourses is critical. Hazelcast offers powerful distributed data structures and concurrency primitives out of the box. In this article, we'll explore how to leverage HMap in combination with AsyncContext in the Hazelcast .NET client to implement non-blocking, fault-tolerant coordination patterns across your cluster.
Key Concepts
HMap: Distributed Key/Value Store
A HMap is Hazelcast's distributed implementation of a key/value dictionary, partitioned across cluster members for scalability and resilience. It provides asynchronous operations (PutAsync
, GetAsync
, TryLockAsync
e.t.c) that return tasks, ensuring non-blocking I/O on the client side (hazelcast hmap doc).
AsyncContext: Flowing Lock Ownership
Starting with Hazelcast .Net Client 4.x, synchronous thread-bound locks were replaced by AsyncContext, which represents a logical execution for lock ownership. Locks acquired within an AsyncContext
flow with awaited operations, ensuring that the lock remains held across threads until explicitly released (hazelcast locking doc).
Why Combine HMap with AsyncContext?
- Non-Blocking: Leverage asynchronous APIs to avoid thread starvation in high-load environments.
- Safe Locking: Maintain lock ownership across
await
boundaries without manual context passing. - Cluster-wide Coordination: Implement distributed locks, semaphores or leader election without external systems.
Getting Started
Prerequisites
- .NET 8 SDK
- Hazelcast .NET Client 5.5.0+
- A running Hazelcast cluster (on localhost or in your environment)
Client Setup
using Hazelcast;
using Hazelcast.DistributedObjects;
using Hazelcast.Core;
//Build options and start client
var options = new HazelcastOptionsBuilder().Build();
await using var client = await HazelcastClientFactory.StartNewClientAsync(options);
Implementing a Distributed Lock with HMap and AsyncContext
Suppose you need a cluster-wide lock on a resource identified by a key. Here's how to do it safely and asynchronously:
using Hazelcast.Locking;
using Hazelcast.Threading;
public class DistributedLockService
{
private readonly IHazelcastClient _client;
private readonly IHMap<string, object> _map;
public DistributedLockService(IHazelcastClient client)
{
_client = client;
_map = await client.GetMapAsync<string, object>("coordination-map");
}
public async Task<TResult> ExecuteWithLockAsync<TResult>(string key, Func<Task<TResult>> action)
{
// Create a new async context for lock scope
using (AsyncContext.New())
{
// Acquire the lock on the key
await _map.LockAsync(key);
try
{
// Perform the protected action
return await action();
}
finally
{
// Always release the lock
await _map.UnlockAsync(key);
}
}
}
}
- AsyncContext.New(): Starts a new logical context for lock flow.
- LockAsync/UnlockAsync: Asynchronously acquires and releases the lock on the map key.
- Protected Action: Your business logic that must run exclusively per key.
Advanced Coordination Patterns
Distributed Semaphores
Use an integer counter stored in HMap and AsyncContext to implement semaphores that limit concurrency to N callers
Leader Election
Store a timestamp and ID in HMap, use TryLockAsync
with a short lease to elect a leader among services, renewing periodically with an AsyncContext.
Testing and Validation
- Unit Tests: Mock IHMap to simulate concurrent calls, ensuring
UnlockAsync
always executes. - Integration Tests: Deploy multiple client instances, verify exclusive execution of
ExecuteWithLockAsync
per key under load.
Conclusion
By combining Hazelcast's HMap with the AsyncContext in the .NET client, you can build robust, non-blocking coordination primitives (distributed locks, semaphores or leader election) without external dependencies. This pattern scales with your cluster and ensures safe concurrency across asynchronous operations.
Checkout Hazelcast's distributed data structures and CP subsystem for even richer coordination capabilities here.