I have a service exposed via WCF MSMQ transport.
Part of the work this service does is it looks up an item based on a key (source, item_id). If it finds one it retrieves the database identifier and uses that to update the record. If it does not find one it inserts the new record.
I noticed it is possible for two items to come in at the same time, both see that an item does not exist in the database, and they both try to insert, but one fails with a constraint error.
I'd like to limit access to the database lookup and following code based on a key (source, item_id) so that only one thread can do the work at a time for that specific key.
I've put together some code to make this happen, but I'd like to get some feedback on if this works or if there is a better approach.
Code to use LockManager
class:
public class ItemService
{
private static LockManager lockManager = new LockManager();
public void AddItem(Item item){
var itemKey = item.Source + ":" + item.ItemId;
lockManager.Work(itemKey, delegate(){ do stuff });
}
}
LockManager
class:
public class LockManager
{
private readonly IDictionary<string, LockObject> _lockTable =
new Dictionary<string, LockObject>();
public void Work(string key, Action work)
{
var lockObject = BorrowLockObject(key);
try
{
lock (lockObject)
{
work();
}
}
finally
{
ReturnLockObject(lockObject);
}
}
private LockObject BorrowLockObject(string key)
{
lock (_lockTable)
{
LockObject lockObject = null;
if (_lockTable.ContainsKey(key))
{
lockObject = _lockTable[key];
}
else
{
lockObject = new LockObject(key);
_lockTable[key] = lockObject;
}
lockObject.Open();
return lockObject;
}
}
private void ReturnLockObject(LockObject lockObject)
{
lock (_lockTable)
{
if (lockObject.Close())
{
_lockTable.Remove(lockObject.GetKey());
}
}
}
}
LockObject
class:
public class LockObject
{
private readonly string _key;
private int _count;
public LockObject(string key)
{
_key = key;
_count = 0;
}
public string GetKey()
{
return _key;
}
public void Open()
{
lock(this)
{
_count++;
}
}
/// <summary>
/// Closes this lock object.
/// </summary>
/// <returns>True if this Lock Object is no longer in use.</returns>
public bool Close()
{
lock(this)
{
_count--;
return _count == 0;
}
}
}
A Java synchronized block marks a method or a block of code as synchronized. A synchronized block in Java can only be executed a single thread at a time (depending on how you use it). Java synchronized blocks can thus be used to avoid race conditions.
A Synchronized block is a piece of code that can be used to perform synchronization on any specific resource of the method. A Synchronized block is used to lock an object for any shared resource and the scope of a synchronized block is smaller than the synchronized method.
Note that constructors cannot be synchronized — using the synchronized keyword with a constructor is a syntax error. Synchronizing constructors doesn't make sense, because only the thread that creates an object should have access to it while it is being constructed.
A synchronized collection of Pair<Key,Action>
and a WorkManager
on a separate thread working on that as a queue (back to front) would simplify this greatly. You could pop out and discard all Pairs that contain the same key after popping one to work on and finishing work on it (locking the collection when doing this).
>
>
BTW: public delegate void Worker();
has a shortcut in Action
.
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