假设我想允许并行执行某些代码,但需要其他代码等待所有这些操作完成。
除了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,我对概念问题最感兴趣,而不是其他可能解决这个问题的集合。
答案 0 :(得分: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。您在评论中提到该值可能为null,因此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();
}