Coordinating Distributed Operations with Hazelcast HMap and AsyncContext in .NET

.Net Core Aug 26, 2024

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);
            }
        }
    }
}
  1. AsyncContext.New(): Starts a new logical context for lock flow.
  2. LockAsync/UnlockAsync: Asynchronously acquires and releases the lock on the map key.
  3. 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.

Tags

Views: Loading...