C# 在 asp.net 中锁定缓存的最佳方法是什么?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/39112/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What is the best way to lock cache in asp.net?
提问by John Owen
I know in certain circumstances, such as long running processes, it is important to lock ASP.NET cache in order to avoid subsequent requests by another user for that resource from executing the long process again instead of hitting the cache.
我知道在某些情况下,例如长时间运行的进程,锁定 ASP.NET 缓存很重要,以避免其他用户对该资源的后续请求再次执行长进程而不是访问缓存。
What is the best way in c# to implement cache locking in ASP.NET?
c# 中在 ASP.NET 中实现缓存锁定的最佳方法是什么?
采纳答案by a7drew
Here's the basic pattern:
这是基本模式:
- Check the cache for the value, return if its available
- If the value is not in the cache, then implement a lock
- Inside the lock, check the cache again, you might have been blocked
- Perform the value look up and cache it
- Release the lock
- 检查缓存中的值,如果可用则返回
- 如果该值不在缓存中,则实施锁
- 锁里面,再查一下缓存,可能被锁了
- 执行值查找并缓存它
- 释放锁
In code, it looks like this:
在代码中,它看起来像这样:
private static object ThisLock = new object();
public string GetFoo()
{
// try to pull from cache here
lock (ThisLock)
{
// cache was empty before we got the lock, check again inside the lock
// cache is still empty, so retreive the value here
// store the value in the cache here
}
// return the cached value here
}
回答by khebbie
Craig Shoemaker has made an excellent show on asp.net caching: http://polymorphicpodcast.com/shows/webperformance/
Craig Shoemaker 在 asp.net 缓存方面做了出色的展示:http: //polymorphicpodcast.com/shows/webperformance/
回答by Jon Limjap
This article from CodeGuru explains various cache locking scenarios as well as some best practices for ASP.NET cache locking:
CodeGuru 的这篇文章解释了各种缓存锁定方案以及 ASP.NET 缓存锁定的一些最佳实践:
回答by Seb Nilsson
I saw one pattern recently called Correct State Bag Access Pattern, which seemed to touch on this.
我最近看到了一种称为正确状态包访问模式的模式,它似乎与此有关。
I modified it a bit to be thread-safe.
我稍微修改了它以确保线程安全。
http://weblogs.asp.net/craigshoemaker/archive/2008/08/28/asp-net-caching-and-performance.aspx
http://weblogs.asp.net/craigshoemaker/archive/2008/08/28/asp-net-caching-and-performance.aspx
private static object _listLock = new object();
public List List() {
string cacheKey = "customers";
List myList = Cache[cacheKey] as List;
if(myList == null) {
lock (_listLock) {
myList = Cache[cacheKey] as List;
if (myList == null) {
myList = DAL.ListCustomers();
Cache.Insert(cacheKey, mList, null, SiteConfig.CacheDuration, TimeSpan.Zero);
}
}
}
return myList;
}
回答by John Owen
For completeness a full example would look something like this.
为了完整起见,一个完整的例子看起来像这样。
private static object ThisLock = new object();
...
object dataObject = Cache["globalData"];
if( dataObject == null )
{
lock( ThisLock )
{
dataObject = Cache["globalData"];
if( dataObject == null )
{
//Get Data from db
dataObject = GlobalObj.GetData();
Cache["globalData"] = dataObject;
}
}
}
return dataObject;
回答by user378380
Just to echo what Pavel said, I believe this is the most thread safe way of writing it
只是为了回应 Pavel 所说的,我相信这是最线程安全的编写方式
private T GetOrAddToCache<T>(string cacheKey, GenericObjectParamsDelegate<T> creator, params object[] creatorArgs) where T : class, new()
{
T returnValue = HttpContext.Current.Cache[cacheKey] as T;
if (returnValue == null)
{
lock (this)
{
returnValue = HttpContext.Current.Cache[cacheKey] as T;
if (returnValue == null)
{
returnValue = creator(creatorArgs);
if (returnValue == null)
{
throw new Exception("Attempt to cache a null reference");
}
HttpContext.Current.Cache.Add(
cacheKey,
returnValue,
null,
System.Web.Caching.Cache.NoAbsoluteExpiration,
System.Web.Caching.Cache.NoSlidingExpiration,
CacheItemPriority.Normal,
null);
}
}
}
return returnValue;
}
回答by nfplee
I have come up with the following extension method:
我想出了以下扩展方法:
private static readonly object _lock = new object();
public static TResult GetOrAdd<TResult>(this Cache cache, string key, Func<TResult> action, int duration = 300) {
TResult result;
var data = cache[key]; // Can't cast using as operator as TResult may be an int or bool
if (data == null) {
lock (_lock) {
data = cache[key];
if (data == null) {
result = action();
if (result == null)
return result;
if (duration > 0)
cache.Insert(key, result, null, DateTime.UtcNow.AddSeconds(duration), TimeSpan.Zero);
} else
result = (TResult)data;
}
} else
result = (TResult)data;
return result;
}
I have used both @John Owen and @user378380 answers. My solution allows you to store int and bool values within the cache aswell.
我已经使用了 @John Owen 和 @user378380 的答案。我的解决方案允许您在缓存中存储 int 和 bool 值。
Please correct me if there's any errors or whether it can be written a little better.
如果有错误或者是否可以写得更好一点,请纠正我。
回答by Michael Logutov
I've wrote a library that solves that particular issue: Rocks.Caching
我写了一个解决这个特定问题的库:Rocks.Caching
Also I've blogged about this problem in details and explained why it's important here.
我还在博客上详细介绍了这个问题,并解释了为什么它在这里很重要。
回答by Tar?k ?zgün Güner
I modified @user378380's code for more flexibility. Instead of returning TResult now returns object for accepting different types in order. Also adding some parameters for flexibility. All the idea belongs to @user378380.
我修改了@user378380 的代码以获得更大的灵活性。现在不是返回 TResult,而是返回用于按顺序接受不同类型的对象。还添加了一些参数以提高灵活性。所有的想法都属于@user378380。
private static readonly object _lock = new object();
//If getOnly is true, only get existing cache value, not updating it. If cache value is null then set it first as running action method. So could return old value or action result value.
//If getOnly is false, update the old value with action result. If cache value is null then set it first as running action method. So always return action result value.
//With oldValueReturned boolean we can cast returning object(if it is not null) appropriate type on main code.
public static object GetOrAdd<TResult>(this Cache cache, string key, Func<TResult> action,
DateTime absoluteExpireTime, TimeSpan slidingExpireTime, bool getOnly, out bool oldValueReturned)
{
object result;
var data = cache[key];
if (data == null)
{
lock (_lock)
{
data = cache[key];
if (data == null)
{
oldValueReturned = false;
result = action();
if (result == null)
{
return result;
}
cache.Insert(key, result, null, absoluteExpireTime, slidingExpireTime);
}
else
{
if (getOnly)
{
oldValueReturned = true;
result = data;
}
else
{
oldValueReturned = false;
result = action();
if (result == null)
{
return result;
}
cache.Insert(key, result, null, absoluteExpireTime, slidingExpireTime);
}
}
}
}
else
{
if(getOnly)
{
oldValueReturned = true;
result = data;
}
else
{
oldValueReturned = false;
result = action();
if (result == null)
{
return result;
}
cache.Insert(key, result, null, absoluteExpireTime, slidingExpireTime);
}
}
return result;
}
回答by cwills
There is no need to lock the whole cache instance, rather we only need to lock the specific key that you are inserting for. I.e. No need to block access to the female toilet while you use the male toilet :)
不需要锁定整个缓存实例,而我们只需要锁定您要插入的特定密钥。即使用男厕所时无需阻止进入女厕所:)
The implementation below allows for locking of specific cache-keys using a concurrent dictionary. This way you can run GetOrAdd() for two different keys at the same time - but not for the same key at the same time.
下面的实现允许使用并发字典锁定特定的缓存键。通过这种方式,您可以同时为两个不同的键运行 GetOrAdd() - 但不能同时为同一个键运行。
using System;
using System.Collections.Concurrent;
using System.Web.Caching;
public static class CacheExtensions
{
private static ConcurrentDictionary<string, object> keyLocks = new ConcurrentDictionary<string, object>();
/// <summary>
/// Get or Add the item to the cache using the given key. Lazily executes the value factory only if/when needed
/// </summary>
public static T GetOrAdd<T>(this Cache cache, string key, int durationInSeconds, Func<T> factory)
where T : class
{
// Try and get value from the cache
var value = cache.Get(key);
if (value == null)
{
// If not yet cached, lock the key value and add to cache
lock (keyLocks.GetOrAdd(key, new object()))
{
// Try and get from cache again in case it has been added in the meantime
value = cache.Get(key);
if (value == null && (value = factory()) != null)
{
// TODO: Some of these parameters could be added to method signature later if required
cache.Insert(
key: key,
value: value,
dependencies: null,
absoluteExpiration: DateTime.Now.AddSeconds(durationInSeconds),
slidingExpiration: Cache.NoSlidingExpiration,
priority: CacheItemPriority.Default,
onRemoveCallback: null);
}
// Remove temporary key lock
keyLocks.TryRemove(key, out object locker);
}
}
return value as T;
}
}