I have a generic Dictionary that I am using as a cache in a threaded .NET project (C#). I will be doing a lot of reads on the dictionary (potentially hundreds or more per second at peak times).
I have a simple Get(int id) method that should return a CustomObject if it's in the cache, or null otherwise.
My question: Is it faster/more efficient to lock the dictionary, or just use a try/catch block?
Assuming the dictionary is stored in a variable named "dic".
Lock Sample:
public CustomObject Get(int id)
{
lock(dic)
{
if (dic.ContainsKey(id))
return dic[id];
}
return null;
}
Try/Catch Sample:
public CustomObject Get(int id)
{
try
{
return dic[id];
}
catch
{
return null;
}
}
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.
No, it's not thread safe. Add and Count may be executed at the "same" time. You have two different lock objects.
C# lock in thread The lock keyword is used to get a lock for a single thread. A lock prevents several threads from accessing a resource simultaneously. Typically, you want threads to run concurrently. Using the lock in C#, we can prevent one thread from changing our code while another does so.
Avoid using 'lock keyword' on string object String object: Avoid using lock statements on string objects, because the interned strings are essentially global in nature and may be blocked by other threads without your knowledge, which can cause a deadlock.
I think you should test it in your own environment. Basically:
So now the question is, how often you expect to have cache-miss, and therefore get an exception thrown. I would go for lock() as it's execution time is not dependent on whether you will or not get cache-hit, which means it's more predictable and measurable, while still - very cheap. I don't think that hundreds hits per second would be any problem.
Simple tests I've made indicate, that getting cache-miss with try/catch is very, very expensive.
Edit:
Simple test shows that:
Which means, got for lock(), because it's more efficient then try/catch if you're getting more then 1 cache miss per few thousands tries, and it's much more stable, being not depended on luck.
You can go ahead and write off the try-catch option. I do not know if it is slower or not, but I do know that it will not always yield correct, consistent, and predictable results if there is another thread updating the Dictionary
. The problem is that at some point the writer will have the Dictionary
in a half-baked state and there is no telling what the readers will see. This just will not work.
Option 1: If .NET 4.0 is available to you then I would use ConcurrentDictionary
.
Option 2: If you are using .NET 3.5 then you can download the Reactive Extensions backport. ConcurrentDictionary
is included in System.Threading.dll.
Option 3: Another idea is to keep two separate copies of the Dictionary
. One would only be used for reading while the other would serve as the official copy that accepts updates. Anytime you update the "official" Dictionary
you would clone it and overwrite the reference of the copy.
public class Example
{
// This is the official version which can accept updates.
private readonly Dictionary<int, CustomObject> official = new Dictionary<int, CustomObject>();
// This is a readonly copy. This must be marked as volatile for this to work correctly.
private volatile Dictionary<int, CustomObject> copy = new Dictionary<int, CustomObject>();
public class Example()
{
}
public void Set(int id, CustomObject value)
{
lock (official)
{
// Update the official dictionary.
official[id] = value;
// Now create a clone of the official dictionary.
var clone = new Dictionary<int, CustomObject>();
foreach (var kvp in official)
{
clone.Add(kvp.Key, kvp.Value);
}
// Swap out the reference.
copy = clone;
}
}
public CustomObject Get(int id)
{
// No lock is required here.
CustomObject value = null;
if (copy.TryGetValue(id, out value))
{
return value;
}
return null;
}
}
This option does not work well if there are a lot of items in the Dictionary
or if updates to the official copy happen frequently. But, it is a trick I do use from time to time.
Option 4: An equally reasonable approach would be to stick with the plain old lock
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With