C#
deadlock detection
multithreading
concurrency
coding best practices

How to check possibility of deadlock in c code

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

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

Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions