c#asp.net多个实例之间的选择性锁定

时间:2014-11-27 14:48:58

标签: c# asp.net-mvc concurrency

假设有以下C#POCO:

public class Gift
{
   public int Id { get; set; }
   public string Owner { get; set; }
}

以下服务:

public class GiftClaimService
{
    private ISomeRepository _someRepository; // Going to be injected or whatever

    public bool ClaimGift(int giftId, string myName)
    {
        var gift = _someRepository.Get(giftId);
        if (gift != null && gift.Owner == null)
        {
            gift.Owner = myName;
            _someRepository.Save(gift);
            return true;
        }
        return false;
    }
}

我希望这很容易解决。任何使用正确ID呼叫“ClaimGift”的用户都可以申请礼品,在这种情况下,他的名字将作为礼品的所有者。使用的存储库超出了范围,如果是数据库等也没关系。

现在这种情况显然会遇到一些并发问题。拥有GiftClaimServiceISomeRepository自己实例的两个或多个用户可能会在几乎同一时间尝试申请礼物,从而导致该方法出现问题(例如,多次覆盖所有者,撤回错误的结果或你能想象到的任何结果。)

我想通过创建一个“全局”锁定对象来锁定它,这将阻止其他人(其他线程/实例等)获取同一个锁,仅限于类型(Gift)和它是Id。对于不同的ID,这可能会“同时”(虽然不是在同一个实例中)。

提交更改后,锁定将由其所有者释放(通过调用存储库中的Save)。对于其他人,它的行为与通常的锁一样:处理在此命令处于暂停状态。新方法如下所示:

public bool ClaimGift(int giftId, string myName)
{
    var result = false;

    // Lock it up (Pseudo-command)... I have no idea how this could work?
    lock(typeof(Gift), giftId)
    {
        var gift = _someRepository.Get(giftId);
        if (gift != null && gift.Owner == null)
        {
            gift.Owner = myName;
            _someRepository.Save(gift);
            result = true;
        }
    }

    return result;
}

这就是我希望它看起来如何,是否有任何常见的技术/模式如何解决这个问题?

2 个答案:

答案 0 :(得分:1)

void Main()
{
    var s = new GiftClaimService ();
    s.ClaimGift(1,"Me");
}

public class Gift
{
   public int Id { get; set; }
   public string Owner { get; set; }
}
public interface ISomeRepository
{
    Gift Get(int giftId);
    void Save(Gift gift);
}
public class GiftClaimService
{
    private ISomeRepository _someRepository; // Going to be injected or whatever
   public readonly static Dictionary<int, string> _claimsInProgress = new Dictionary<int, string>();
   private static readonly object _locker = new object();

    public bool ClaimGift(int giftId, string myName)
    {
        lock (_locker)
        {
        if ( _claimsInProgress.ContainsKey(giftId))
            return false;
        }

        var gift = _someRepository.Get(giftId);
        if (gift == null)
            return false; // no such gift

        lock (_locker)
        {
            if ( _claimsInProgress.ContainsKey(giftId))
                return false;// someone claimed it just now

            _claimsInProgress.Add(giftId, myName);
        }
        bool retValue;
        if (gift.Owner == null)
        {
            gift.Owner = myName;
            _someRepository.Save(gift);
            retValue = true;
        }
        else
            retValue = false;

        lock (_locker)
        {
            _claimsInProgress.Remove(giftId);
        }           

        return retValue;
    }
}

答案 1 :(得分:0)

如果要处理Web场方案,则必须在数据库级别处理。否则你可以尝试下面的代码:

public class GiftClaimService
{
    private static readonly object Lock = new object();
    private ISomeRepository _someRepository; // Going to be injected or whatever

    public bool ClaimGift(int giftId, string myName)
    {
        lock (Lock)
        {
            var gift = _someRepository.Get(giftId);
            if (gift != null && gift.Owner == null)
            {
                gift.Owner = myName;
                _someRepository.Save(gift);
                return true;
            }
            return false;
        }
    }
}