C#
deadlock detection
multithreading
concurrency
coding best practices

How to check possibility of deadlock in c code

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

A deadlock occurs when two or more threads are blocked forever, each waiting for a lock held by the other. In C#, deadlocks commonly happen with lock statements, Monitor, Mutex, SemaphoreSlim, and async/await patterns. Detecting and preventing deadlocks requires understanding the four conditions that must all be present: mutual exclusion, hold and wait, no preemption, and circular wait.

Classic Deadlock Example

csharp
1private static readonly object LockA = new();
2private static readonly object LockB = new();
3
4// Thread 1
5void Method1()
6{
7    lock (LockA)          // Acquires A
8    {
9        Thread.Sleep(100); // Simulates work, gives Thread 2 time to acquire B
10        lock (LockB)       // Waits for B — Thread 2 holds B
11        {
12            Console.WriteLine("Thread 1 done");
13        }
14    }
15}
16
17// Thread 2
18void Method2()
19{
20    lock (LockB)          // Acquires B
21    {
22        Thread.Sleep(100);
23        lock (LockA)       // Waits for A — Thread 1 holds A
24        {
25            Console.WriteLine("Thread 2 done");
26        }
27    }
28}
29// DEADLOCK: Thread 1 holds A, waits for B. Thread 2 holds B, waits for A.

Detection Method 1: Consistent Lock Ordering

The most effective prevention is acquiring locks in the same global order everywhere:

csharp
1// FIXED: Both methods acquire LockA first, then LockB
2void Method1()
3{
4    lock (LockA)
5    {
6        lock (LockB)
7        {
8            Console.WriteLine("Thread 1 done");
9        }
10    }
11}
12
13void Method2()
14{
15    lock (LockA)  // Same order as Method1
16    {
17        lock (LockB)
18        {
19            Console.WriteLine("Thread 2 done");
20        }
21    }
22}

To enforce this programmatically, assign a numeric order to each lock:

csharp
1class OrderedLock
2{
3    private readonly object _lock = new();
4    public int Order { get; }
5
6    [ThreadStatic] private static int _lastAcquiredOrder;
7
8    public OrderedLock(int order) => Order = order;
9
10    public IDisposable Acquire()
11    {
12        if (Order <= _lastAcquiredOrder)
13            throw new InvalidOperationException(
14                $"Lock order violation: acquiring {Order} while holding {_lastAcquiredOrder}");
15
16        Monitor.Enter(_lock);
17        _lastAcquiredOrder = Order;
18        return new LockReleaser(this);
19    }
20
21    private class LockReleaser : IDisposable
22    {
23        private readonly OrderedLock _parent;
24        public LockReleaser(OrderedLock parent) => _parent = parent;
25        public void Dispose()
26        {
27            _lastAcquiredOrder = 0;
28            Monitor.Exit(_parent._lock);
29        }
30    }
31}
32
33// Usage
34var lockA = new OrderedLock(1);
35var lockB = new OrderedLock(2);
36
37using (lockA.Acquire())
38using (lockB.Acquire())  // OK: 2 > 1
39{
40    // Work
41}
42
43using (lockB.Acquire())
44using (lockA.Acquire())  // Throws: 1 <= 2 — lock order violation!
45{
46}

Detection Method 2: Timeouts

Use Monitor.TryEnter with a timeout to detect potential deadlocks at runtime:

csharp
1void SafeMethod()
2{
3    bool lockATaken = false;
4    bool lockBTaken = false;
5
6    try
7    {
8        lockATaken = Monitor.TryEnter(LockA, TimeSpan.FromSeconds(5));
9        if (!lockATaken)
10        {
11            Console.WriteLine("Warning: could not acquire LockA — possible deadlock");
12            return;
13        }
14
15        lockBTaken = Monitor.TryEnter(LockB, TimeSpan.FromSeconds(5));
16        if (!lockBTaken)
17        {
18            Console.WriteLine("Warning: could not acquire LockB — possible deadlock");
19            return;
20        }
21
22        // Do work
23    }
24    finally
25    {
26        if (lockBTaken) Monitor.Exit(LockB);
27        if (lockATaken) Monitor.Exit(LockA);
28    }
29}

Detection Method 3: async/await Deadlocks

The most common C# deadlock in modern code involves async/await with .Result or .Wait():

csharp
1// DEADLOCK in ASP.NET (pre-.NET Core) and WinForms/WPF
2public ActionResult Index()
3{
4    var data = GetDataAsync().Result;  // DEADLOCK!
5    return View(data);
6}
7
8private async Task<string> GetDataAsync()
9{
10    await Task.Delay(100);  // After delay, tries to resume on UI/request thread
11    return "data";          // But that thread is blocked by .Result
12}

Fix: Use async all the way down, or use ConfigureAwait(false):

csharp
1// Fix 1: async all the way
2public async Task<ActionResult> Index()
3{
4    var data = await GetDataAsync();  // No deadlock
5    return View(data);
6}
7
8// Fix 2: ConfigureAwait(false) in library code
9private async Task<string> GetDataAsync()
10{
11    await Task.Delay(100).ConfigureAwait(false);  // Don't capture context
12    return "data";
13}

Detection Method 4: Visual Studio Debugger

When a deadlock occurs during debugging:

  1. Debug → Break All (Ctrl+Alt+Break)
  2. Debug → Windows → Threads — shows all threads and their states
  3. Debug → Windows → Parallel Stacks — visualizes thread call stacks
  4. Look for threads in "Waiting" state with stack traces showing Monitor.Enter or WaitOne

Detection Method 5: Static Analysis

Use tools that detect potential deadlocks at compile time:

csharp
1// Roslyn Analyzers detect some patterns
2// Install: Microsoft.CodeAnalysis.FxCopAnalyzers
3
4// Thread Sanitizer (for C/C++)
5// Clang's -fsanitize=thread
6
7// .NET-specific tools:
8// - CHESS (Microsoft Research) - systematic concurrency testing
9// - PostSharp Threading - compile-time deadlock detection

Detection Method 6: Resource Wait Graph

Build a wait-for graph at runtime and check for cycles:

csharp
1class DeadlockDetector
2{
3    private static readonly Dictionary<int, object> _threadHoldsLock = new();
4    private static readonly Dictionary<int, object> _threadWaitsForLock = new();
5
6    public static void OnLockAcquired(object lockObj)
7    {
8        _threadHoldsLock[Thread.CurrentThread.ManagedThreadId] = lockObj;
9        _threadWaitsForLock.Remove(Thread.CurrentThread.ManagedThreadId);
10    }
11
12    public static void OnLockWaiting(object lockObj)
13    {
14        _threadWaitsForLock[Thread.CurrentThread.ManagedThreadId] = lockObj;
15        CheckForCycle();
16    }
17
18    private static void CheckForCycle()
19    {
20        // Build wait-for graph and detect cycles
21        // If thread A waits for lock X, and thread B holds lock X
22        // and thread B waits for lock Y, and thread A holds lock Y → cycle!
23    }
24}

Common Pitfalls

  • Blocking on async code: Calling .Result, .Wait(), or .GetAwaiter().GetResult() on async methods is the #1 cause of deadlocks in modern C#. Use await instead.
  • Locking on this or typeof: lock(this) or lock(typeof(MyClass)) allows external code to lock on the same object, creating unexpected contention. Always lock on private readonly objects.
  • Nested locks: Acquiring multiple locks in different orders across methods is hard to detect through code review. Use the OrderedLock pattern or reduce the number of locks.
  • Thread pool starvation: Not a classic deadlock, but blocking all thread pool threads (e.g., with .Result inside Task.Run) prevents other work from completing, creating a deadlock-like hang.
  • Debugging intermittent deadlocks: Deadlocks may only reproduce under specific timing. Use stress testing and tools like CHESS or Coyote for systematic exploration of thread interleavings.

Summary

  • Prevent deadlocks by acquiring locks in a consistent global order
  • Use Monitor.TryEnter with timeouts to detect deadlocks at runtime instead of hanging forever
  • Never call .Result or .Wait() on async methods — use await all the way
  • Use Visual Studio's Parallel Stacks and Threads windows to diagnose deadlocks during debugging
  • Lock only on private readonly object fields, never on this, typeof, or string literals

Course illustration
Course illustration

All Rights Reserved.