开发者

How do I synchronize a block of code based on a key?

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 cla开发者_开发技巧ss 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 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).

>

  • client adds
    • lock collection
    • add
    • unlock collection

>

  • backthread iteration:
    • lock collection
    • get work item (last in collection)
    • remove from collection
    • unlock collection
    • work ... (in the meanwhile clients add more, and maybe duplicates)
    • lock collection
    • remove all items with same key (and handle properly)
    • unlock collection

BTW: public delegate void Worker(); has a shortcut in Action.


This works. 2 things: The dictionary will never release keys and values; If you want to acquire two locks at the same time be sure to avoid deadlocks by accesseing them always in the same order (sort the keys).

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜