2014-11-27 3 views
0

Предполагая, что существует следующие C# POCO:C# asp.net селективная блокировка между несколькими экземплярами

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; 
    } 
} 

Я надеюсь, что это легко unterstand. Любой пользователь, вызывающий «ClaimGift» с правильным идентификатором, может требовать подарок, в этом случае его имя ставится как Владелец подарка. Используемый репозиторий выходит за рамки, не имеет значения, есть ли база данных и т. Д.

Теперь этот сценарий, очевидно, имеет некоторые проблемы с параллелизмом. Два или более пользователя, имеющие собственные экземпляры GiftClaimService и ISomeRepository, могут попытаться получить подарок практически в одно и то же время, вызывая некоторые проблемы с этим методом (например, переопределение владельца несколько раз, сохранение неправильного результата или того, что вы можете себе представить).

Я хотел бы заблокировать это, создав «глобальный» объект блокировки, который не позволит другим (другим потокам/экземплярам и т. Д.) Получить тот же замок, ограниченный типом (Gift), и это 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; 
} 

Это, как я хочу это может выглядеть, есть ли общий метод/шаблон, как решить эту проблему?

+0

просто добавить столбец временных меток на подарочной таблице, а затем EF будет проверять при обновлении, если это значение столбцов было изменено или нет, и вы получите хороший исключение. – hazimdikenli

+0

@hazimdikenli OK Я уже реализовал такой оптимистичный параллелизм в EF раньше, но это совсем не то же самое, это о попытке предотвратить этот сценарий вообще, плюс репозиторий не является ни EF, ни SQL Server (в настоящее время его MongoDB, но Я не хочу, чтобы это решение было специфичным для базы данных) – thmshd

+0

о добавлении глобального массива/списка претензий, выполняемых с идентификатором подарка, и доступа к нему с помощью объекта-замка. – hazimdikenli

ответ

0

Если вы хотите обрабатывать сценарий веб-фермы, это нужно будет обрабатывать на уровне базы данных. В противном случае вы можете попробовать этот код:

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; 
     } 
    } 
} 
+1

Веб-ферма - хороший момент. Но, возможно, это еще не должно быть на уровне базы данных. Это просто должен быть уровень «достаточно глобальный» по всем экземплярам ... только некоторые думали. – thmshd

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; 
    } 
} 
+0

Спасибо, хотя это первый подход, это не совсем то, что я хочу, потому что это фактически не блокировка. Здесь метод предполагает, что, когда блокировка установлена ​​для данного 'giftId', она должна возвращаться, потому что« кто-то утверждал это сейчас ». Это предположение не является неизбежным. (в этом упрощенном сценарии * возможно *, но представьте, что это, возможно, не востребовано бесплатно, а продано, и процесс также будет проверять баланс пользователя и т. д.), – thmshd

Смежные вопросы