Selective Lock Mechanism By ConcurrentDictionary(C#)

Cem Yasar
4 min readAug 2, 2020

In this previous post I shared how we should implement lock mechanism. Here, I will share how to implement selective lock mechanism in C#.

If we put a lock above on a code block we block the other threads to enter into the locked code block. So, lets imagine, we need to lock a code block for a value and not to block for other values. In one thread we may lock a code block for an input value and we must not lock this code block for another input value but if another value is sent we will also lock the code block for this new input value. For instance, there could be doing asynchronous processes for customers. Many processes want to execute the same code block for the same customer but we need to lock this code block, so only one process/thread can enter to the code block for the specific customer. Other processes for other customers are allowed to enter to the code block, however again they are locked after one of them enters to the code block. We are grouping the threads. Yellow threads, green threads, orange threads… When one of the yellow thread is entered to the code block, any other yellow threads are not allowed to enter, but other green and orange threads are allowed to, however, when a green thread is entered, other green threads are not allowed also.

How can we group those threads? By using static dictionary. We need a key for grouping and a reference type for locking. Key is the input value, may be the social security number of a customer or any other unique value.

Lets imagine there is an e-signing code block. More than one user will sign a document of a customer. As we know, e-sign is a complex mechanism and must be done sequentailly. You may put the signers in order, so they will sign one after another and you can get rid of using any lock mechanism. Yet, how do you put signers in an order? Sometimes, it is not preferred. Sometimes, when a user may have started to sign a document for a customer, others must not allowed to initiate signing code block. System may warn them and they start to sign another customers’ documents.

So, have a look at this code block:

static ConcurrentDictionary<string, string> DicLocks = new ConcurrentDictionary<string, string>();public string TestMethod(string input){
if(DicLocks.ContainsKey(input)){
return "IsLocked";
}
//isAdded is true if key is newly added, false if already added
bool isAdded = DicLocks.TryAdd(input, input);
if(!isAdded){
return "IsLocked";
}

/*... Critical code block ...*/
string removeValue;
DicLocks.TryRemove(input, out removeValue);
}

You have realized that there is not any lock keyword used. Since, Concurrent types in .NET framework is thread-safe. You can see TryAdd, TryRemove methods fo ConcurrentDictionary. These methods quarantee only one thread can add a key-value pair, then return false, means that key is already added. Concurrent — thread-safe types provide lock mechanisms within themselves, so there is no need to use lock keyword or blocks.

Also, there is no concurrent hashset implementation in .NET framework, so we use dictionary.

Edit: Also, if you want the threads to wait for reaching a source, you can use lock() function and pass specific objects to the lock() function for the specific threads. By doing so, you do not have to return immediate responses such as the example above. You create specific objects for the specific sources and make the threads to wait for initial executions. For instance:

static ConcurrentDictionary<string, object> DicLocks = new ConcurrentDictionary<string, object>();static Dictionary<string, DataSourceMock> DicDataSources = new ConcurrentDictionary<string, DataSourceMock>();public DataSourceMock TestMethod(string sourceId){   //if datasource exists
if(DicDataSources.ContainsKey(sourceId)){
return DicDataSources.get(sourceId);
}
DicLocks.TryAdd(sourceId, new Object());//a new object is created for that specificId//only the first executor thread of this function by using specific sourceId have inserted a new object and locks it. Other incoming executor threads for the specific sourceId will wait the first thread. If the first thread has done the initial executions of the specific source
if(DicLocks.ContainsKey(sourceId)){
lock(DicLocks.get(sourceId)){
//only the initial thread will get into this condition, since the value in the DicLocks will be deleted by initial thread also after creating datasource for the specific sourceId.
if(DicLocks.ContainsKey(sourceId)){
DicDataSources.add(sourceId, new DataSourceMock(/*properties*/));
Object removedObject;
DicLocks.TryRemove(sourceId, out removedObject);
}
}
}
//Maybe, this TestMethod will be used to create dataSources only once and return always. So initial thread for the specific sourceId must create dataSource and including initial thread and others will get this datasource by TestMethod.
if(DicDataSources.ContainsKey(sourceId)){
return DicDataSources.get(sourceId);
}
return null; //Note: this code was not tested and also having missing parts
}

However, dictionary type has some drawbacks, concurrent dictionary has more. As you know, dictionary is resizable element and if you continue to fill many new elements conitnuously, it will cause continuously resizing operations. ConcurrentDictionary already has a constructor to give initial appropriate size and concurrency level. It is mainly suggested to use default empty constructor but if you really sure the maximum possible size of the dictionary and concurrency-level size you can use these parameters, so you may increase the performance. For more details you can read these articles:

Inside of the normal dictionary:
https://www.red-gate.com/simple-talk/blogs/the-net-dictionary/

Concurrent Dictionary:
https://www.red-gate.com/simple-talk/blogs/inside-the-concurrent-collections-concurrentdictionary/

--

--