Start here. This is the direct spoken answer to practice first.
Why this question matters
One global lock protects correctness but destroys unrelated concurrency. Keyed coordination narrows the boundary, yet it introduces lifecycle and distributed-scope problems that need an explicit design.
I would map each stable resource key to a gate such as a SemaphoreSlim(1, 1). An operation waits asynchronously for that key, performs only the state transition that must be serialized, and releases the gate in finally. Operations for different keys use different gates and can continue concurrently. The key must represent the real invariant, so two spellings or aliases for the same customer cannot produce separate locks.