2016-06-10 2 views
0

Предположим, что я хотел бы разрешить параллельное выполнение некоторого кода, но для завершения всех этих операций нужен другой код.Предварительные замки в C#?

Давайте представим, в дополнении к softlock к lock:

public static class MySimpleCache 
{ 
    private static readonly SynchronizedCollection<KeyValuePair<string, string>> Collection = new SynchronizedCollection<KeyValuePair<string, string>>(); 

    public static string Get(string key, Func<string> getter) 
    { 
     // Allow parallel enumerations here, 
     // but force modifications to the collections to wait. 
     softlock(Collection.SyncRoot) 
     { 
      if (Collection.Any(kvp => kvp.Key == key)) 
      { 
       return Collection.First(kvp => kvp.Key == key).Value; 
      } 
     } 

     var data = getter(); 

     // Wait for previous soft-locks before modifying the collection and let subsequent softlocks wait 
     lock (Collection.SyncRoot) 
     { 
      Collection.Add(new KeyValuePair<string, string>(key, data)); 
     } 
     return data; 
    } 
} 

Есть ли дизайн-шаблон или язык/рамочные функции в C#/NET, чтобы достичь этого в простом и надежном способе, или бы один есть. реализовать это с нуля?

В настоящее время я ограничен .NET 3.5, и меня в основном интересует концептуальная проблема, а не столько в других возможных сборниках, которые могли бы решить этот пример сами по себе.

+3

Похоже, вам нужно [ReaderWriterLockSlim] (https://msdn.microsoft.com/en-us/library/system.threading.readerwriterlockslim (v = vs.110) .aspx), который может начаться, как блокировки считывателя и быть обновлены до блокировки записи. –

+0

Ваш поиск немного неэффективен, вы перечисляете дважды, вы можете использовать 'var result = Collection.FirstOrDefault (kvp => kvp.Key == key); if (result! = default (KeyValuePair <строка, строка>)) {return result.Value; } 'и только один раз выполнить поиск. –

+0

@MatthewWatson Спасибо, это выглядит очень подходящим, я буду играть с ним. – Alex

ответ

2

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

public static class MySimpleCache 
{ 
    private static readonly SynchronizedCollection<KeyValuePair<string, string>> Collection = new SynchronizedCollection<KeyValuePair<string, string>>(); 
    private static readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim(); 

    public static string Get(string key, Func<string> getter) 
    { 
     //This allows multiple readers to run concurrently. 
     Lock.EnterReadLock(); 
     try 
     { 
      var result = Collection.FirstOrDefault(kvp => kvp.Key == key); 
      if (!Object.Equals(result, default(KeyValuePair<string, string>))) 
      { 
       return result.Value; 
      } 
     } 
     finally 
     { 
      Lock.ExitReadLock(); 
     } 


     var data = getter(); 

     //This blocks all future EnterReadLock(), once all finish it allows the function to continue 
     Lock.EnterWriteLock(); 
     try 
     { 
      Collection.Add(new KeyValuePair<string, string>(key, data)); 
      return data; 
     } 
     finally 
     { 
      Lock.ExitWriteLock(); 
     } 
    } 
} 

Однако, вы можете захотеть, чтобы проверить, в то время как вы, где ждут, чтобы взять блокировки записи кто-то еще, возможно, вошел в запись в кэшу, в этом случае вы можете использовать EnterUpgradeableReadLock(), это позволяет неограниченное количество людей быть внутри EnterReadLock(), но только один человек может находиться в замке обновления (и не будет никаких блокировок записи). Блокировка, пригодная для обновления, полезна, когда вы знаете, что скорее всего будете писать, но есть возможность не писать.

public static class MySimpleCache 
{ 
    private static readonly SynchronizedCollection<KeyValuePair<string, string>> Collection = new SynchronizedCollection<KeyValuePair<string, string>>(); 
    private static readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim(); 

    public static string Get(string key, Func<string> getter) 
    { 
     //This allows multiple readers to run concurrently. 
     Lock.EnterReadLock(); 
     try 
     { 
      var result = Collection.FirstOrDefault(kvp => kvp.Key == key); 
      if (!Object.Equals(result, default(KeyValuePair<string, string>))) 
      { 
       return result.Value; 
      } 
     } 
     finally 
     { 
      Lock.ExitReadLock(); 
     } 

     //This allows unlimited EnterReadLock to run concurrently, but only one thread can be in upgrade mode, other threads will block. 
     Lock.EnterUpgradeableReadLock(); 
     try 
     { 
      //We need to check to see if someone else filled the cache while we where waiting. 
      var result = Collection.FirstOrDefault(kvp => kvp.Key == key); 
      if (!Object.Equals(result, default(KeyValuePair<string, string>))) 
      { 
       return result.Value; 
      } 


      var data = getter(); 

      //This blocks all future EnterReadLock(), once all finish it allows the function to continue 
      Lock.EnterWriteLock(); 
      try 
      { 
       Collection.Add(new KeyValuePair<string, string>(key, data)); 
       return data; 
      } 
      finally 
      { 
       Lock.ExitWriteLock(); 
      } 
     } 
     finally 
     { 
      Lock.ExitUpgradeableReadLock(); 
     } 
    } 
} 

P.S. Вы упомянули в комментарии, что значение может быть нулевым, поэтому FirstOrDefault() не будет работать. В этом случае используйте метод расширения для создания функции TryFirst().

public static class ExtensionMethods 
{ 
    public static bool TryFirst<T>(this IEnumerable<T> @this, Func<T, bool> predicate, out T result) 
    { 
     foreach (var item in @this) 
     { 
      if (predicate(item)) 
      { 
       result = item; 
       return true; 
      } 
     } 
     result = default(T); 
     return false; 
    } 
} 

//Used like 
Lock.EnterReadLock(); 
try 
{ 
    KeyValuePair<string, string> result; 
    bool found = Collection.TryFirst(kvp => kvp.Key == key, out result); 
    if (found) 
    { 
     return result.Value; 
    } 
} 
finally 
{ 
    Lock.ExitReadLock(); 
} 
Смежные вопросы