concurrency-and-shared-statelisted
Install: claude install-skill Sarmkadan/dotnet-senior-skills
# Concurrency and Shared State
## First question: does this state need to be shared?
Most "how do I lock this" reviews end with removing the shared state: make the service scoped instead of singleton, pass values through the call chain, or use immutable snapshots. A singleton with mutable fields is guilty until proven thread-safe - and "proven" means every access site audited, not "it hasn't crashed yet". Races surface under production load as corrupted state, not as test failures.
## lock discipline
```csharp
// WRONG: check and act are separate; two threads both pass the check
if (!_cache.ContainsKey(key)) { _cache[key] = Create(key); }
// RIGHT: the whole read-modify-write under one lock (or use ConcurrentDictionary.GetOrAdd)
lock (_gate) { if (!_cache.TryGetValue(key, out var v)) { v = Create(key); _cache[key] = v; } }
```
- Lock object: `private readonly Lock _gate = new();` (.NET 9+) or `private readonly object _gate = new();`. Never `lock (this)`, `lock (typeof(X))`, or lock on a string - all reachable by other code, all deadlock bait.
- Hold locks for nanoseconds, not milliseconds: no I/O, no callbacks, no unknown virtual calls inside a lock. A lock around an HTTP call serializes your whole service.
- `await` inside `lock` does not compile - and the workaround people reach for (`Monitor.Enter` manually) is broken, because the continuation resumes on a different thread that does not own the monitor. Async mutual exclusion is `SemaphoreSlim(1, 1)`:
```csharp
await