tags:

views:

102

answers:

2

I have a mapping where each key could have multiple associated values. I thought that a ConcurrentDictionary might help me more easily code this map for use in a multithreaded environment, but the methods seem to be built around a single value. I see that AddOrUpdate() lets me modify the value if it already exists, but it doesn't guarantee atomicity for that operation so it seems pointless? Does anyone have a good strategy for tackling this situation?

Sorry, I guess I was being a bit vague. I'd like to have multiple values for a key, ie have an IList associated with the key. But I want to be able to add/remove values from the multi-value in a safe manner. It just looked like the AddOrUpdate + delegate method might result in things getting lost if multiple calls to it were made at the same-ish time?

+1  A: 

I thought that AddOrUpdate was atomic, but it looks like it's not atomic with regard to the delegate. Sorry!

A reference that might help: http://blogs.msdn.com/b/pfxteam/archive/2009/11/06/9918363.aspx

mquander
You are wrong. The documentation states that the call to the delegate is NOT made under the lock because of the unpredictability of user code.
evilfred
From your link: "Atomic with regards to other mutating methods on the collection (e.g. TryAdd/TryUpdate/TryRemove/etc.), excluding the execution of the user-provided delegate. "
evilfred
OK, I see what you mean. I skimmed it and misinterpreted. Edited post to reflect.
mquander
I don't believe that the link tells the whole story. Please look at the code.
Steven Sudit
A: 

It looks like both AddOrUpdate and TryUpdate will work.

edit

I may well be mistaken. If so, I don't think the documentation is clear enough to say, so let's just look at the code. Courtesy of reflector:

public TValue AddOrUpdate(TKey key, Func<TKey, TValue> addValueFactory, Func<TKey, TValue, TValue> updateValueFactory)
{
    TValue local;
    TValue local3;
    if (key == null)
    {
        throw new ArgumentNullException("key");
    }
    if (addValueFactory == null)
    {
        throw new ArgumentNullException("addValueFactory");
    }
    if (updateValueFactory == null)
    {
        throw new ArgumentNullException("updateValueFactory");
    }
    do
    {
        if (!this.TryGetValue(key, out local3))
        {
            TValue local2;
            local = addValueFactory(key);
            if (!this.TryAddInternal(key, local, false, true, out local2))
            {
                continue;
            }
            return local2;
        }
        local = updateValueFactory(key, local3);
    }
    while (!this.TryUpdate(key, local, local3));
    return local;
}

Now, if the update factory took an existing list and returned a new one with an additional member, it does indeed look to me as though it will be atomic. In the event of a race condition, the loser will simply have their update factory called again. Am I mistaken?

Steven Sudit
Why does TryAddInternal return something different than what we put in?
evilfred
Most likely TryAddInternal returns the actual value for key, whether or not it returns false (i.e. -- regardless of whether the second parameter was added or not).
Colonel Kernel
If we don't know what TryAddInternal does then reading that code is like reading tea leaves.
evilfred
Colonel: You are correct. It sets `local2` to local *unless* there's already a value for that `key`, in which case it sets `local2` to that existing value.
Steven Sudit
So I think this does what I want then? Just to confirm: If I have a race condition, I am guaranteed that in one of the cases the new value will be made and added to the dictionary. In the other case this added value will eventually be updated. However, the add factory could get called multiple times, and the update factory could get called multiple times. However, if the "value" that is being updated is a ConcurrentBag then I don't care if it's updated multiple times, so I should be guaranteed that when the race is resolved I have items from both threads in my bag?
evilfred
In a word, yes.
Steven Sudit