В подобных ситуациях вы можете использовать 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();
}
Похоже, вам нужно [ReaderWriterLockSlim] (https://msdn.microsoft.com/en-us/library/system.threading.readerwriterlockslim (v = vs.110) .aspx), который может начаться, как блокировки считывателя и быть обновлены до блокировки записи. –
Ваш поиск немного неэффективен, вы перечисляете дважды, вы можете использовать 'var result = Collection.FirstOrDefault (kvp => kvp.Key == key); if (result! = default (KeyValuePair <строка, строка>)) {return result.Value; } 'и только один раз выполнить поиск. –
@MatthewWatson Спасибо, это выглядит очень подходящим, я буду играть с ним. – Alex