A Shared lock Cost the Same as a Private One at One Thread, and Not at Two
On .NET 10.0.12 a shared lock matched a per-thread lock at one thread and cost more from two up, where Monitor.LockContentionCount left zero.

10 articles
Long-form writing on C#, the .NET runtime, and the frameworks built on them.
2026 · 4 articles
On .NET 10.0.12 a shared lock matched a per-thread lock at one thread and cost more from two up, where Monitor.LockContentionCount left zero.

On EF Core 10.0.12, AsEnumerable() before Where().Count() sent a full-table SELECT instead of COUNT(*) and allocated 1056.36 KB against 49.53 KB at 1,000 rows.

In a dotnet-dump of a contested lock, clrstack -all marked no thread as the owner. syncblk named it, and a suspended await appeared only in dumpasync.

Environment.ProcessorCount is the CPU quota rounded up, never below 1. The thread pool minimum follows it, and Server GC switches itself off at 1.

2026 · 3 articles
Assigning a struct to an interface allocated 0 bytes. Passing it across a call boundary allocated 24. A constrained generic allocated none, until its body asked for an object.

Removing UseRouting dropped per-request allocation by 2.29 KB in isolation — separated from authorization's share, routing itself costs about 24 bytes.

A thread pool queue held at two thousand work items read 1986 through one provider and -13 through the other, in the same second, in the same process.

2026 · 3 articles
Server GC finished 2.7 times faster and collected 17 times less often — and made the slowest request in the run 5 to 13 times slower. Both numbers are real.

Blocking on a task is the case the thread pool compensates for. Blocking on a sleep or an event is the case it cannot see, and it finished 2.5 times slower.

An async method that never suspends allocates nothing. The same method, suspended once, allocates 160 bytes. The cost is not the keyword — it is the suspension.
