97

Am I right in thinking this is the correct use of a Concurrent Dictionary

private ConcurrentDictionary<int,long> myDic = new ConcurrentDictionary<int,long>();

//Main thread at program startup

for(int i = 0; i < 4; i++)
{
  myDic.Add(i, 0);
}

//Separate threads use this to update a value

myDic[InputID] = newLongValue;

I have no locks etc and am just updating the value in the dictionary even though multiple threads might be trying to do the same.

Luis Gouveia
  • 8,334
  • 9
  • 46
  • 68
Jon
  • 38,814
  • 81
  • 233
  • 382
  • 2
    It depends - does `newLongValue` depend on the previous value of `myDic[InputID]`? – Damien_The_Unbeliever Nov 22 '11 at 11:55
  • 3
    you should avoid to access by the key directly `myDic[InputID]` for race condition. You should try `GetOrAdd` – Olivier Albertini Sep 04 '16 at 23:17
  • 3
    @OlivierAlbertini, I do not think `myDic[InputID]` causes any problem when it is used as an lvalue. `GetOrAdd` is not a correct replacement since it adds only if value does not exist. We can instead use `AddOrUpdate` to add/update same value in dictionary. – Jatin Sanghvi Jul 09 '17 at 00:10
  • @JatinSanghvi Old response here, but I want to say that yes using the accessor directly like that causes problems as it bypasses the atomic operations. As per the documentation: "Store a key/value pair in the dictionary unconditionally, and overwrite the value of a key that already exists". The key word is unconditionally, there is no point using a concurrent dictionary if you are avoiding the concurrent/atomic functionality while updating. – LimpingNinja Jul 10 '21 at 15:44

5 Answers5

86

It depends on what you mean by thread-safe.

From MSDN - How to: Add and Remove Items from a ConcurrentDictionary:

ConcurrentDictionary<TKey, TValue> is designed for multithreaded scenarios. You do not have to use locks in your code to add or remove items from the collection. However, it is always possible for one thread to retrieve a value, and another thread to immediately update the collection by giving the same key a new value.

So, it is possible to get an inconsistent view of the value of an item in the dictionary.

Oded
  • 489,969
  • 99
  • 883
  • 1,009
  • 2
    Thats an interesting point! Would you still use a lock in that scenario? – Jon Nov 22 '11 at 11:30
  • 1
    @Jon - It depends on your application and whether that's OK will you. But I would say that if you want consistent views of items, you would need to wrap each read and update of an item in a lock. – Oded Nov 22 '11 at 11:34
  • 15
    I think this is not what the doc says. The inconsistency has to do with what the view contains, if the view is just the value, then it is perfectly consistent. As long as you get the value of a key, the key's value in the dictionary could change. This is as inconsistent as the DateTime.Now value. – George Mavritsakis Jun 20 '14 at 20:58
4

Best way to find this out is check MSDN documentation.

For ConcurrentDictionary the page is http://msdn.microsoft.com/en-us/library/dd287191.aspx

Under thread safety section, it is stated "All public and protected members of ConcurrentDictionary(Of TKey, TValue) are thread-safe and may be used concurrently from multiple threads."

So from concurrency point of view you are okay.

Erdem
  • 95
  • 1
  • 5
2

Yes, you are right.

That and the possibility to enumerate the dictionary on one thread while changing it on another thread are the only means of existence for that class.

Jan
  • 15,802
  • 5
  • 35
  • 59
  • 10
    What I'd to to add is that [here](http://blogs.msdn.com/b/pfxteam/archive/2010/01/08/9945809.aspx) is helpful info of how and when to use `ConcurrentDictionary`. – alex.b Nov 22 '11 at 11:17
1

It depends, in my case I prefer using this method.

ConcurrentDictionary<TKey, TValue>.AddOrUpdate Method (TKey, Func<TKey, TValue>, Func<TKey, TValue, TValue>);

See MSDN Library for method usage details.

Sample usage:

results.AddOrUpdate(
  Id,
  id => new DbResult() {
     Id = id,
     Value = row.Value,
     Rank = 1
  },
  (id, v) =>
  {
     v.Rank++;
     return v;
  });
Onur
  • 852
  • 9
  • 18
  • 5
    FYI: "When you supply a value factory method (to the GetOrAdd and AddOrUpdate methods), it can actually run and have its result discarded afterwards (because some other thread had won the race)." More info here: https://arbel.net/2013/02/03/best-practices-for-using-concurrentdictionary/ – keremispirli Mar 19 '16 at 12:43
  • 2
    Yes, you're right, as it is noted in remarks section "If you call AddOrUpdate simultaneously on different threads, addValueFactory may be called multiple times, but its key/value pair might not be added to the dictionary for every call." So you need to be sure you're not generating multiple persistent objects. – Onur Sep 04 '16 at 19:27
  • And if you need to update contents, not changing the stored object entirely, for example to change a property of a previously added object, this method is useful, otherwise you need to use locks or other synchronization methods. – Onur Sep 04 '16 at 19:47
1

Just a note: Does not justify using a ConcurrentDicitonary object with a linear loop, making it underutilized. The best alternative is to follow the recommendations of the Microsoft Documentation, as mentioned by Oded using Parallelism, according to the example below:

Parallel.For(0, 4, i => 
{
   myDic.TryAdd(i, 0);
});
Antonio Leonardo
  • 1,805
  • 1
  • 8
  • 18