Earlz
Earlz

Reputation: 63935

Thread safe way of reading a value from a dictionary that may or may not exist

So, I've been spoiled by ConcurrentDictionary and it's awesome TryGetValue method. However, I'm constrained to using only regular Dictionary because this is in a portable class library targeting phone and other platforms. I'm trying to write a very limited subset of a Dictionary and exposing it in a thread-safe manner.

I basically need something like GetOrAdd from ConcurrentDictionary. Right now, I have this implemented like:

        lock (lockdictionary)
        {
            if (!dictionary.ContainsKey(name))
            {
                value = new foo();
                dictionary[name] = value;
            }
            value = dictionary[name];
        }

Is this basically as good as I can get it? I think locking is only really required if the key doesn't exist and it gets added, however, there is no good "get value if it exists, return null otherwise" method. If I were to leave out the ContainsKey bit, when the key didn't exist I'd get an exception because the key doesn't exist.

Is there anyway I could get this to a more lean version? Or is this just the best a regular dictionary can do?

Upvotes: 3

Views: 3568

Answers (5)

Alexei Levenkov
Alexei Levenkov

Reputation: 100630

This is as good as it gets.

Locking is required because dictionary makes no guarantees that you can update and read in parallel at all. Even single call to get element running at the same time as update on other thread may fail due to changes to internal data strucures.

Note that the behavior is explicitly covered in Thread Safety section of Dictionary

A Dictionary can support multiple readers concurrently, as long as the collection is not modified. Even so, enumerating through a collection is intrinsically not a thread-safe procedure. In the rare case where an enumeration contends with write accesses, the collection must be locked during the entire enumeration. To allow the collection to be accessed by multiple threads for reading and writing, you must implement your own synchronization.

Upvotes: 0

Steve Westbrook
Steve Westbrook

Reputation: 1708

You can use a double-check pattern, as follows:

if (!dictionary.ContainsKey(name))
{
    lock (lockdictionary)
    {
        if (!dictionary.ContainsKey(name))
        {
            value = new foo();
            dictionary[name] = value;
        }
        value = dictionary[name];
    }
}

This ensures you only lock if you actually need to, but also ensures once you have locked that you still need to add the value. The performance should be better than always locking. But don't take my word for it. Run a test!

Upvotes: 0

Peter Ritchie
Peter Ritchie

Reputation: 35869

You could try using ReaderWriterLockSlim. For example:

ReaderWriterLockSlim locker = new ReaderWriterLockSlim();

//..

public string GetOrAdd(string name)
{
    locker.EnterUpgradeableReadLock();
    try
    {
        if(!dictionary.ContainsKey(name))
        {
            locker.EnterWriteLock();
            try
            {
                dictionary[name] = new foo();
            }
            finally
            {
            locker.ExitWriteLock();
            }
        }
        value = dictionary[name];
    }
    finally
    {
        locker.ExitUpgradeableReadLock();
    }
    return value;
}

Upvotes: 1

usr
usr

Reputation: 171246

Locking is required even for reading in the presence of concurrent writers. So yes, this is as good as it gets if you mutate the dictionary.

You can of course always create a copy of the entire dictionary each time something is written. That way readers might see an out-of-date version but they can safely read.

Upvotes: 2

undefined
undefined

Reputation: 1364

Your implementation is just fine. Note, that lock implementation has neglictable performance penalty in case of uncontended access. However, in order to achieve true thread-safety you must use lock with EVERY operation with dictionary - I suggest to write wrapper class, like SynchronizedDictinory to keep sync logic in one place

Upvotes: 0

Related Questions