Is it possible for a Dictionary in .Net to cause dead lock when reading and writing to it in parallel?

So your code is executing Dictionary.FindEntry. It's not a deadlock - a deadlock happens when two threads block in a way which makes them wait for one another to release a resource, but in your case you're getting two seemingly infinite loops. The threads aren't locked.

Let's take a look at this method in the reference source:

private int FindEntry(TKey key) {
    if( key == null) {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
    }

    if (buckets != null) {
        int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF;
        for (int i = buckets[hashCode % buckets.Length]; i >= 0; i = entries[i].next) {
            if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) return i;
        }
    }
    return -1;
}

Take a look at the for loop. The increment part is i = entries[i].next, and guess what: entries is a field which is updated in the Resize method. next is a field of the inner Entry struct:

public int next;        // Index of next entry, -1 if last

If your code can't exit the FindEntry method, the most probable cause would be you've managed to mess the entries in such a way that they produce an infinite sequence when you're following the indexes pointed by the next field.

As for the Insert method, it has a very similar for loop:

for (int i = buckets[targetBucket]; i >= 0; i = entries[i].next)

As the Dictionary class is documented to be non-threadsafe, you're in the realm of undefined behavior anyway.

Using a ConcurrentDictionary or a locking pattern such as a ReaderWriterLockSlim (Dictionary is thread-safe for concurrent reads only) or a plain old lock nicely solves the problem.


Looks like a race condition (not a deadlock) - which, as you comment, causes the messed up internal state.

The dictionary is not thread safe so concurrent reads and writes to the same container from seperate threads (even if there are as few as one of each) is not safe.

Once the race condition is hit, it becomes undefined what will happen; in this case what appears to be an infinite loop of some sort.

In general, once write access is required, some form of synchronization is required.