dotnet-csharp-concurrency-patterns
Guidance on synchronization primitives and thread-safe data structures for .NET 8.0+ applications.
Install
mkdir -p .claude/skills/dotnet-csharp-concurrency-patterns && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/12930" && unzip -o skill.zip -d .claude/skills/dotnet-csharp-concurrency-patterns && rm skill.zipInstalls to .claude/skills/dotnet-csharp-concurrency-patterns
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Synchronizes threads and protects shared state. lock, SemaphoreSlim, Interlocked, concurrent collections.Key capabilities
- →Synchronize threads using `lock` or `Monitor`
- →Protect shared state with `SemaphoreSlim`
- →Perform atomic scalar operations with `Interlocked`
- →Manage thread-safe key-value lookups with `ConcurrentDictionary`
- →Implement thread-safe FIFO queues with `ConcurrentQueue`
- →Handle many-readers/few-writers scenarios with `ReaderWriterLockSlim`
How it works
The skill provides a decision framework to select the simplest concurrency primitive based on the shared state type and critical section characteristics.
Inputs & outputs
When to use dotnet-csharp-concurrency-patterns
- →Implement thread-safe collections
- →Use Interlocked for scalar updates
- →Apply locking strategies
- →Choose concurrency primitives
About this skill
dotnet-csharp-concurrency-patterns
Thread synchronization primitives, concurrent data structures, and a decision framework for choosing the right
concurrency mechanism. Covers lock/Monitor, SemaphoreSlim, Interlocked, ConcurrentDictionary,
ConcurrentQueue, ReaderWriterLockSlim, and SpinLock. This skill is the authoritative source for synchronization
and thread-safe data access patterns.
Version assumptions: .NET 8.0+ baseline. All primitives covered are available from .NET Core 1.0+ but examples use modern C# idioms.
Scope
- lock/Monitor, SemaphoreSlim, and Interlocked patterns
- ConcurrentDictionary, ConcurrentQueue, and concurrent collections
- ReaderWriterLockSlim and SpinLock for advanced scenarios
- Concurrency primitive decision framework
Out of scope
- Async/await and Task-based patterns -- see [skill:dotnet-csharp-async-patterns]
- Producer/consumer with Channel<T> -- see [skill:dotnet-channels]
- Naming and style conventions -- see [skill:dotnet-csharp-coding-standards]
Cross-references: [skill:dotnet-csharp-async-patterns] for async/await patterns, [skill:dotnet-channels] for producer/consumer, [skill:dotnet-csharp-coding-standards] for naming conventions.
Concurrency Primitive Decision Framework
Choose the simplest primitive that meets the requirement. Complexity increases downward:
Is the shared state a single scalar (int, long, reference)?
YES -> Use Interlocked (lock-free, lowest overhead)
Is the shared state a key-value lookup or queue?
YES -> Use ConcurrentDictionary / ConcurrentQueue (thread-safe by design)
Does the critical section contain `await`?
YES -> Use SemaphoreSlim (async-compatible via WaitAsync)
NO -> Does the critical section need many readers, few writers?
YES -> Use ReaderWriterLockSlim (only if profiling shows lock contention)
NO -> Use lock (simplest, lowest cognitive overhead)
Is the critical section extremely short (< 100 ns) with high contention?
YES -> Consider SpinLock (advanced, measure first)
```text
### Quick Reference Table
| Primitive | Async-Safe | Reentrant | Use Case |
| --------------------------- | ----------------- | -------------------------------- | ------------------------------------------------------ |
| `lock` / `Monitor` | No | Yes (same thread) | Short critical sections without `await` |
| `SemaphoreSlim` | Yes (`WaitAsync`) | No | Async-compatible mutual exclusion, throttling |
| `Interlocked` | N/A (lock-free) | N/A | Atomic scalar operations (increment, compare-exchange) |
| `ConcurrentDictionary<K,V>` | N/A (thread-safe) | N/A | Thread-safe key-value cache/lookup |
| `ConcurrentQueue<T>` | N/A (thread-safe) | N/A | Thread-safe FIFO queue |
| `ReaderWriterLockSlim` | No | Optional (`LockRecursionPolicy`) | Many-readers/few-writers (profile-driven only) |
| `SpinLock` | No | No | Ultra-short critical sections under extreme contention |
---
## lock and Monitor
`lock` is syntactic sugar for `Monitor.Enter`/`Monitor.Exit`. Use it for short, synchronous critical sections.
### Correct Usage
```csharp
public sealed class Counter
{
private readonly object _lock = new();
private int _count;
public void Increment()
{
lock (_lock)
{
_count++;
}
}
public int GetCount()
{
lock (_lock)
{
return _count;
}
}
}
```text
### Lock Object Rules
| Rule | Rationale |
| --------------------------------------- | ------------------------------------------------------------------ |
| Use a private, dedicated `object` field | Prevents external code from locking on the same object |
| Never lock on `this` | Any external code with a reference can cause deadlocks |
| Never lock on `typeof(T)` | Global lock shared by all code in the AppDomain |
| Never lock on string literals | String interning means different code may share the same reference |
| Never lock on value types | Boxing creates a new object each time -- lock is never acquired |
### Monitor.Wait / Monitor.Pulse
For signaling between threads (producer/consumer without `Channel<T>`):
```csharp
public sealed class BoundedBuffer<T>
{
private readonly Queue<T> _queue = new();
private readonly object _lock = new();
private readonly int _maxSize;
public BoundedBuffer(int maxSize) => _maxSize = maxSize;
public void Enqueue(T item)
{
lock (_lock)
{
while (_queue.Count >= _maxSize)
Monitor.Wait(_lock);
_queue.Enqueue(item);
Monitor.Pulse(_lock);
}
}
public T Dequeue()
{
lock (_lock)
{
while (_queue.Count == 0)
Monitor.Wait(_lock);
var item = _queue.Dequeue();
Monitor.Pulse(_lock);
return item;
}
}
}
```text
For modern code, prefer `Channel<T>` (see [skill:dotnet-channels]) over Monitor.Wait/Pulse.
---
## SemaphoreSlim
The only built-in .NET synchronization primitive that supports `await`. Use it whenever a critical section contains
async operations.
### Mutual Exclusion (1,1)
```csharp
public sealed class AsyncCache
{
private readonly SemaphoreSlim _semaphore = new(1, 1);
private readonly Dictionary<string, object> _cache = new();
public async Task<T> GetOrAddAsync<T>(string key,
Func<CancellationToken, Task<T>> factory,
CancellationToken ct = default)
{
await _semaphore.WaitAsync(ct);
try
{
if (_cache.TryGetValue(key, out var existing))
return (T)existing;
var value = await factory(ct);
_cache[key] = value!;
return value;
}
finally
{
_semaphore.Release();
}
}
}
```text
### Throttling (N concurrent operations)
```csharp
public sealed class ThrottledProcessor
{
private readonly SemaphoreSlim _throttle;
public ThrottledProcessor(int maxConcurrency)
=> _throttle = new SemaphoreSlim(maxConcurrency, maxConcurrency);
public async Task ProcessAllAsync(IEnumerable<WorkItem> items,
CancellationToken ct = default)
{
var tasks = items.Select(async item =>
{
await _throttle.WaitAsync(ct);
try
{
await ProcessItemAsync(item, ct);
}
finally
{
_throttle.Release();
}
});
await Task.WhenAll(tasks);
}
private Task ProcessItemAsync(WorkItem item, CancellationToken ct) =>
Task.CompletedTask; // implementation
}
```text
### SemaphoreSlim Disposal
`SemaphoreSlim` implements `IDisposable`. Dispose it when the owning object is disposed:
```csharp
public sealed class ManagedResource : IDisposable
{
private readonly SemaphoreSlim _semaphore = new(1, 1);
public void Dispose() => _semaphore.Dispose();
}
```text
---
## Interlocked Operations
Lock-free atomic operations for scalar values. The lowest-overhead synchronization mechanism.
### Common Operations
```csharp
private int _counter;
private long _totalBytes;
private object? _current;
// Atomic increment / decrement
Interlocked.Increment(ref _counter);
Interlocked.Decrement(ref _counter);
// Atomic add
Interlocked.Add(ref _totalBytes, bytesRead);
// Atomic exchange -- returns the old value
var previous = Interlocked.Exchange(ref _current, newValue);
// Compare-and-swap -- only writes if current value matches expected
var original = Interlocked.CompareExchange(ref _counter,
newValue: 10,
comparand: 0); // Sets to 10 only if current value is 0
```text
### Volatile Read/Write
For visibility guarantees without atomicity (reading the latest value written by another thread):
```csharp
private int _flag;
// Write with release semantics (all prior writes visible to readers)
Volatile.Write(ref _flag, 1);
// Read with acquire semantics (sees all writes prior to the last Volatile.Write)
var value = Volatile.Read(ref _flag);
```text
### Interlocked vs volatile vs lock
| Mechanism | Atomicity | Ordering | Use Case |
| --------------------- | ---------------------------------------------------------------------- | --------------- | ------------------------------------- |
| `Interlocked` | Yes | Full fence | Counters, flags, CAS loops |
| `Volatile.Read/Write` | No (single read/write is naturally atomic for aligned <= pointer-size) | Acquire/release | Signal flags, publication patterns |
| `lock` | Yes (for entire block) | Full fence | Multi-step operations on shared state |
---
## ConcurrentDictionary
Thread-safe key-value store. The most commonly used concurrent collection.
### Safe Patterns
```csharp
private readonly ConcurrentDictionary<int, Widget> _cache = new();
// Atomic get-or-add
var widget = _cache.GetOrAdd(id, key => LoadWidget(key));
// Atomic add-or-update
var updated = _cache.AddOrUpdate(id,
addValueFactory: key => CreateDefault(key),
updateValueFactory: (key,
---
*Content truncated.*
When not to use it
- →For async/await and Task-based patterns
- →For producer/consumer with Channel<T>
- →For naming and style conventions
Prerequisites
Limitations
- →The skill does not cover naming and style conventions
How it compares
This workflow offers a structured decision framework for choosing concurrency mechanisms, which is more systematic than ad-hoc selection of synchronization primitives.
Compared to similar skills
dotnet-csharp-concurrency-patterns side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| dotnet-csharp-concurrency-patterns (this skill) | 0 | 5mo | No flags | Advanced |
| csharp-developer | 43 | 2mo | No flags | Advanced |
| csharp-pro | 9 | 4mo | No flags | Intermediate |
| dotnet-backend-patterns | 7 | 5mo | No flags | Advanced |
Try saying
Example prompts that trigger this skill in your AI assistant.
More by rudironsoni
View all by rudironsoni →You might also like
csharp-developer
zenobi-us
Expert C# developer specializing in modern .NET development, ASP.NET Core, and cloud-native applications. Masters C# 12 features, Blazor, and cross-platform development with emphasis on performance and clean architecture.
csharp-pro
sickn33
Write modern C# code with advanced features like records, pattern matching, and async/await. Optimizes .NET applications, implements enterprise patterns, and ensures comprehensive testing. Use PROACTIVELY for C# refactoring, performance optimization, or complex .NET solutions.
dotnet-backend-patterns
wshobson
Master C#/.NET backend development patterns for building robust APIs, MCP servers, and enterprise applications. Covers async/await, dependency injection, Entity Framework Core, Dapper, configuration, caching, and testing with xUnit. Use when developing .NET backends, reviewing C# code, or designing API architectures.
azure-servicebus-dotnet
microsoft
Azure Service Bus SDK for .NET. Enterprise messaging with queues, topics, subscriptions, and sessions. Use for reliable message delivery, pub/sub patterns, dead letter handling, and background processing. Triggers: "Service Bus", "ServiceBusClient", "ServiceBusSender", "ServiceBusReceiver", "ServiceBusProcessor", "message queue", "pub/sub .NET", "dead letter queue".
backend-testing
exceptionless
Backend testing with xUnit, Foundatio.Xunit, integration tests with AppWebHostFactory, FluentClient, ProxyTimeProvider for time manipulation, and test data builders. Keywords: xUnit, Fact, Theory, integration tests, AppWebHostFactory, FluentClient, ProxyTimeProvider, TimeProvider, Foundatio.Xunit, TestWithLoggingBase, test data builders
azure-identity-dotnet
microsoft
Azure Identity SDK for .NET. Authentication library for Azure SDK clients using Microsoft Entra ID. Use for DefaultAzureCredential, managed identity, service principals, and developer credentials. Triggers: "Azure Identity", "DefaultAzureCredential", "ManagedIdentityCredential", "ClientSecretCredential", "authentication .NET", "Azure auth", "credential chain".