Если вы не абсолютно уверены, что крайне важно не иметь избыточных запросов, тогда я бы вообще избежал блокировки.Кэш ASP.NET по своей природе поточно-ориентирован, поэтому единственным недостатком следующего кода является то, что вы можете временно увидеть несколько избыточных запросов, участвующих в гонке друг с другом, когда истечет срок их связанной записи в кэше:
public static string DoSearch(string query)
{
var results = (string)HttpContext.Current.Cache[query];
if (results == null)
{
results = GetResultsFromSlowDb(query);
HttpContext.Current.Cache.Insert(query, results, null,
DateTime.Now.AddHours(1), Cache.NoSlidingExpiration);
}
return results;
}
Если вырешите, что вы действительно должны избегать всех избыточных запросов, тогда вы можете использовать набор более детальных блокировок, по одной блокировке на запрос:
public static string DoSearch(string query)
{
var results = (string)HttpContext.Current.Cache[query];
if (results == null)
{
object miniLock = _miniLocks.GetOrAdd(query, k => new object());
lock (miniLock)
{
results = (string)HttpContext.Current.Cache[query];
if (results == null)
{
results = GetResultsFromSlowDb(query);
HttpContext.Current.Cache.Insert(query, results, null,
DateTime.Now.AddHours(1), Cache.NoSlidingExpiration);
}
object temp;
if (_miniLocks.TryGetValue(query, out temp) && (temp == miniLock))
_miniLocks.TryRemove(query);
}
}
return results;
}
private static readonly ConcurrentDictionary<string, object> _miniLocks =
new ConcurrentDictionary<string, object>();