In this tip, I share a small class that synchronizes multithread access to multiple objects.
Sometimes, we need to provide lock/release access to multiple objects by
string - i.e., list of file names where we don't want to put a lock on the entire list, but need to prevent simultaneous access to each record by name (such as file name or database object).
If there is a list of the objects, it is possible (but probably not considered as a good practice) to put a lock on the object itself when working on it. In other situations (i.e., there is no list of object or the list is changing), it can be a bit tricky.
To help with this issue, I wrote a small class that can do the work - please see the code below:
The Class Code
public class MultiLock
private class SyncObject
public int Count;
public AutoResetEvent Event;
Event = new AutoResetEvent(true);
private Dictionary<string, SyncObject> syncs;
syncs = new Dictionary<string, SyncObject>();
public void Lock(string Name)
if (!syncs.TryGetValue (Name, out so))
so = new SyncObject();
syncs[Name] = so;
public void Release(string Name)
if (!syncs.TryGetValue(Name, out so))
if (Interlocked.Decrement(ref so.Count) == 0)
This class contains the
Dictionary, that maps object name (i.e., file name) to the synchronization object. When one calls the
Lock method, checks whether this name is already in lock, and if it isn't, adds it to the dictionary, otherwise, it will increase the locks
Counter. This action is wrapped into the lock, put on entire dictionary, but it is expected to be very short, just to avoid crash on the collection change from the other thread.
Release method is called, it checks whether it is last lock removed (
Counter == 0) and if yes, it also removes this name from the list.
Here is the sample of the code usage:
static MultiLock ml;
static Dictionary<string, List<int>> keys =
new Dictionary<string, List<int>>();
static void Main(string args)
for (int i = 0; i < 11; i++)
keys["X" + i] = new List<int>();
Stopwatch sw = new Stopwatch();
ml = new MultiLock();
Task tasks = new Task;
for (int i = 0; i < tasks.Length; i++)
int n = i;
string key = keys.Keys.Skip(i % keys.Count).First();
tasks[i] = ThreadFunc(n, key);
Console.WriteLine("Run ended in " +
sw.Elapsed.TotalSeconds.ToString("0.##") + " sec, press <Enter> to exit");
static async Task ThreadFunc(int n, string key)
Random rnd = new Random();
var list = keys[key];
for (int k = 0; k < 100; k++)
if (list.Count > 0)
await Task.Delay(rnd.Next(0, 20));
if (list.Count > 0)
After testing, I discovered that the use of '
async' methods caused the
Monitor.Exit method to throw a
SynchronizationLockException. This was due to the requirement that the
Monitor.Exit method be called from the same thread as its
Monitor.Enter. To resolve this issue, I've replaced the
Monitor with the
AutoresetEvent, which does not have this limitation.
There are also minor changes in the code usage - mostly to shorten the code
- 26th November, 2022: Initial version
- 29th January, 2023: Update
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.