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.
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.
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
AddAsyncandSetAsyncis 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.