Skip to main content

Counters

Counters maintain a shared numeric value that many clients can update at the same time. Every update is atomic, so concurrent increments never lose each other.

Why counters exist

When several instances update the same number, plain read-modify-write logic corrupts the value. Two clients read 5, each adds 1, and both write 6 — one increment is lost.

A counter closes this gap. The store applies each change as a single atomic operation, so the outcome is correct no matter how many clients update it concurrently. You get this without holding a lock or writing retry loops.

Increment or decrement a counter

Use AddAsync to change a counter by a signed amount. The value is applied atomically on the server, so you never read the current value into your process first.

await client.Counters.AddAsync("counter:orders", 1);

Pass a negative delta to decrement:

await client.Counters.AddAsync("counter:orders", -1);

Read a counter

Use GetAsync to read the current value. Like other read operations, it returns a result you check before using Value.

var result = await client.Counters.GetAsync("counter:orders");

if (result.IsSuccess)
{
Console.WriteLine(result.Value);
}

Set a counter to an exact value

Use SetAsync to overwrite a counter with a specific number, for example to seed or reset it.

await client.Counters.SetAsync("counter:orders", 100);

SetAsync is a blind write. It replaces whatever value is stored, discarding any concurrent updates that arrived first.

warning

Do not increment by reading with GetAsync, adding in your code, and writing back with SetAsync. That sequence reintroduces the lost-update race that counters exist to prevent, because another client can change the value between your read and your write. Use AddAsync for every increment or decrement, and reserve SetAsync for seeding or resetting a counter to a known value.

Example: track active workers

This pattern keeps an accurate live count of workers even when many start and stop concurrently. Each worker increments on entry and decrements on exit.

await client.Counters.AddAsync("workers:active", 1);

// do work

await client.Counters.AddAsync("workers:active", -1);

Because both updates go through AddAsync, the count stays correct across all instances.

tip

Pair the decrement with a finally block or equivalent cleanup so a thrown exception still releases the count. A skipped decrement leaks a worker into the total permanently.

Behavior

Counters share the following characteristics.

  • Every AddAsync and SetAsync is atomic on the server, so concurrent updates never interfere.
  • Values are numeric.
  • Behavior is identical in in-process and distributed modes, so code written against one runs unchanged against the other.

When to use counters

Counters fit any case where you need a shared number that many clients update.

  • Tracking counts such as active users, queued jobs, or requests in flight.
  • Rate limiting against a shared quota.
  • Any shared numeric state that must stay correct under concurrency.

Next steps

  • Watch to react to changes in real time.
  • Leases to coordinate ownership across clients.