# Threads, Locks and Thread Safety — C#/.NET

Source: https://www.skillbyai.com/en/dotnet/a-threads

> Protect shared state with locks, Interlocked and concurrent collections.

## Shared mutable state needs protection

When several threads touch the same data, operations like `count++` (read, add, write) can interleave and lose updates: a **race condition**. Options, from simplest: **avoid sharing** (give each task its own data and combine results); use **immutable** data; use **`Interlocked`** for single atomic operations (`Interlocked.Increment(ref count)`); use **concurrent collections** such as `ConcurrentDictionary<TKey, TValue>` and `ConcurrentQueue<T>`; or protect a block with **`lock`**. Since .NET 9, the dedicated **`System.Threading.Lock`** type is the recommended lock object. You cannot `await` inside a `lock` block; for async code use **`SemaphoreSlim`** with `WaitAsync`. **Deadlocks** happen when two threads each hold a lock the other needs; always acquire multiple locks in the same order. For producer-consumer pipelines, **`System.Threading.Channels`** offers async-friendly queues with back-pressure.

## Three ways to count safely

`count++` from many tasks would lose updates; each version below is correct.

```cs
using System.Collections.Concurrent;

int counter = 0;
await Task.WhenAll(Enumerable.Range(0, 100).Select(_ => Task.Run(() =>
{
    for (int i = 0; i < 1000; i++) Interlocked.Increment(ref counter);
})));

var hits = new ConcurrentDictionary<string, int>();
hits.AddOrUpdate("/home", 1, (_, old) => old + 1);

var gate = new Lock();
var log = new List<string>();
void Append(string line)
{
    lock (gate) { log.Add(line); }       // List<T> is not thread-safe
}

var throttle = new SemaphoreSlim(3);     // async-friendly: at most 3 at once
async Task CallApiAsync()
{
    await throttle.WaitAsync();
    try { await Task.Delay(100); }
    finally { throttle.Release(); }
}
```

## Most collections are not thread-safe

`List<T>` and `Dictionary<TKey, TValue>` can be corrupted by concurrent writes, sometimes silently. Use a lock, a concurrent collection, or redesign so only one thread writes.

**Quiz:** Why can you not use `lock` around an `await`?

- [x] The method may resume on a different thread, and the compiler forbids await inside lock; use SemaphoreSlim instead
- [ ] lock is deprecated
- [ ] await is not allowed in classes with locks
- [ ] lock only works with value types

*Answer:* The method may resume on a different thread, and the compiler forbids await inside lock; use SemaphoreSlim instead. Monitor-based locks are thread-affine, so the compiler disallows await inside lock blocks.
