ReaderWriterLockSlim Upgradeable Read Lock in C

In multi-threaded C# development, the ReaderWriterLockSlim class provides an upgradeable read lock mode designed for scenarios where a thread needs to inspect a resource before deciding whether to update it. This article explores what upgradeable read locks are, why they are necessary to prevent deadlocks, the internal mechanics of how they operate, and how to properly implement them in C# with clear code examples.

What Is an Upgradeable Read Lock?

A standard reader-writer lock allows multiple threads to read concurrently or a single thread to write exclusively. However, a common problem arises when a thread holding a standard read lock needs to modify data: it cannot simply upgrade to a write lock. If two threads holding read locks both attempt to upgrade to a write lock simultaneously, a deadlock occurs because each thread waits for the other to release its read lock.

An upgradeable read lock solves this issue. It acts like a standard read lock by allowing other concurrent readers, but it reserves the exclusive right to upgrade to a write lock.

How Upgradeable Read Locks Work

The mechanics of an upgradeable read lock follow strict synchronization rules:

  1. Single Upgradeable Reader: Only one thread can hold the upgradeable read lock at any given time.
  2. Coexistence with Regular Readers: While a thread holds an upgradeable read lock, other threads can still acquire and release standard read locks freely.
  3. Upgrading to Write Mode: When the holder of the upgradeable lock calls EnterWriteLock(), the lock blocks new readers from entering and waits for all active standard readers to finish. Once existing readers exit, the upgradeable lock is promoted to an exclusive write lock.
  4. Downgrading: When the thread calls ExitWriteLock(), it safely downgrades back to an upgradeable read lock. It only relinquishes all lock ownership once ExitUpgradeableReadLock() is invoked.
  5. Deadlock Prevention: Because only one thread can hold an upgradeable read lock, two threads can never race to upgrade at the same time, completely eliminating upgrade-related deadlocks.

Implementing an Upgradeable Read Lock in C

The most common use case is a cache lookup where you read a value, verify whether it exists, and update the cache only if the value is missing.

using System;
using System.Collections.Generic;
using System.Threading;

public class ThreadSafeCache<TKey, TValue>
{
    private readonly Dictionary<TKey, TValue> _cache = new();
    private readonly ReaderWriterLockSlim _lock = new();

    public TValue GetOrCreate(TKey key, Func<TKey, TValue> valueFactory)
    {
        _lock.EnterUpgradeableReadLock();
        try
        {
            // First check: read the data safely alongside other readers
            if (_cache.TryGetValue(key, out TValue existingValue))
            {
                return existingValue;
            }

            // If missing, upgrade to an exclusive write lock
            _lock.EnterWriteLock();
            try
            {
                // Double-check pattern in case another operation updated it
                if (!_cache.TryGetValue(key, out existingValue))
                {
                    existingValue = valueFactory(key);
                    _cache[key] = existingValue;
                }

                return existingValue;
            }
            finally
            {
                // Downgrades back to upgradeable read lock
                _lock.ExitWriteLock();
            }
        }
        finally
        {
            // Releases the lock entirely
            _lock.ExitUpgradeableReadLock();
        }
    }
}

When to Use Upgradeable Read Locks

Use upgradeable read locks when an operation begins as a read but has a moderate to high probability of converting into a write. If writes are extremely rare, acquiring a standard read lock first, releasing it, and acquiring a full write lock (using double-check logic) may offer better read concurrency. However, if the read-then-write pattern is standard for your workflow, the upgradeable read lock provides a robust, deadlock-free solution.