11

Suppose I have this method:

void Foo(int bar)
{
    // do stuff
}

Here is the behavior I want Foo to have:

  1. If thread 1 calls Foo(1) and thread 2 calls Foo(2), both threads can run concurrently.

  2. If thread 1 calls Foo(1) and thread 2 calls Foo(1), both threads cannot run concurrently.

Is there a good, standard way in .net to specify this type of behavior? I have a solution that uses a dictionary of objects to lock on, but that feels kind of messy.

Bradley
  • 257
  • 3
  • 9
  • 1
    before complete to read your question I said: I will use a dictionary... :) – Felice Pollano Jun 23 '11 at 15:56
  • Related: [How to dynamically lock strings but remove the lock objects from memory](https://stackoverflow.com/questions/33786579/how-to-dynamically-lock-strings-but-remove-the-lock-objects-from-memory/65263688#65263688). – Theodor Zoulias Feb 02 '23 at 01:07

4 Answers4

11

Use a dictionary that provides different lock objects for the different arguments. Set up the dictionary when you instantiate the underlying object (or statically, if applicable):

var locks = new Dictionary<int, object>() {
    {1, new Object()},
    {2, new Object()},
    …
};

And then use it inside your method:

void Foo(int bar) {
    lock (locks[bar]) {
        …
    }
}

I wouldn’t say that this solution is messy, on the contrary: providing a fine lock granularity is commendable and since locks on value types don’t work in .NET, having a mapping is the obvious solution.

Be careful though: the above only works as long as the dictionary isn’t concurrently modified and read. It is therefore best to treat the dictionary as read-only after its set-up.

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
  • 2
    Note that this code is only correct if the dictionary is never written while it is being read. – Eric Lippert Jan 18 '18 at 18:08
  • 1
    @EricLippert Good point. When writing the answer I was thinking of a readonly map that's set up once. – Konrad Rudolph Jan 18 '18 at 18:38
  • this answer solves concurrency while accessing dictionary https://stackoverflow.com/a/4954652/2630427. And this answer approaches with ConcurrentDictionary https://stackoverflow.com/a/19009026/2630427 – ilkerkaran Aug 15 '19 at 11:27
3

Bottom line: you can't lock on value types.

The dictionary you're using is the best approach I can think of. It's kludgey, but it works.

Personally, I'd pursue an architectural solution that makes the locking unnecessary, but I don't know enough about your system to give you pointers there.

Randolpho
  • 55,384
  • 17
  • 145
  • 179
2

Using Dictionary is not enough, you should use "ConcurrentDictionary" or implement a data structure that supports multi-thread access.

  • I'd love to see why this answer is downvoted. It suggests that it is wrong but is it? Sounds like reasonable thing to do. – wirher May 23 '18 at 18:43
0

Creating a Dictionary<> so that you can lock on a value seems overkill to me. I got this working using a string. There are people (e.g. Jon Skeet) who do not like this approach (and for valid reasons - see this post: Is it OK to use a string as a lock object?)

But I have a way to mitigate for those concerns: intern the string on the fly and combine it with an unique identifier.

// you should insert your own guid here
string lockIdentifier = "a8ef3042-e866-4667-8673-6e2268d5ab8e";
public void Foo(int bar)
{
    lock (string.Intern(string.Format("{0}-{1}", lockIdentifier, bar)))
    {
        // do stuff
    }
}

What happens is that distinct values are stored in a string intern pool (which crosses AppDomain boundaries). Adding lockIdentifier to the string ensures that the string won't conflict with interned strings used in other applications, meaning the lock will only take effect in your own application.

So the intern pool will return a reference to an interned string - this is ok to lock on.

Community
  • 1
  • 1
CarllDev
  • 1,294
  • 2
  • 19
  • 34
  • 1
    This is really smart. I like it. I am now curious to see what the performance is between intern'ing a string vs a dictionary lookup. Well done with this answer. – TheLegendaryCopyCoder Oct 31 '22 at 13:46