做.NET后台开发的同学,对缓存处理一定不陌生,这里把我混迹C#圈子十余载珍藏的基础类库分享出来,希望能够给刚踏入开发门槛的朋友一些帮助。
后续我会逐步分享基础库的其余部分,先列个大纲:
首先还是科普一下缓存的概念:
什么是Cache对象?
你在生成高性能网络应用程序时所遇到的一个问题就是需要避免重复。一个Cache对象允许在它们第一次被请求时在内存中缓存项,然后将被缓存的副本用于以后的请求。使用被缓存的副本允许你避免重建满足以前请求的信息,特别是那些每次创建时都需占有明示的服务器上的处理器时间的命令。
除缓存个别项,如缓存中的计算结果以外,Asp.net提供一个输出缓存可用于保存网页和用户控件。Cache对象和输出缓存是拥有相同的角色和属性,但它们是皆然不同的两种对象。
ASP.NET提供一个全特性的缓存引擎,用于保存和获取信息片段。Cache对象没有关于它所包含的的项的信息。Cache对象仅包含对于那些对象的引用以及提供它们的依赖项和设置过期策略。Cache还提供方法在相同网络应用程序的网页之间传递值。缓存方法实现自动的锁,所以,值在被多个页并发访问时是安全的。
那么Cache对象是如何工作的呢?
使用Cache对象的的过程是:
1. 页面请求的项被标识为已经在Cache对象中保存。
2. ASP.NET 检查 Cache 对象并用已缓存的版本来决定是否可用。
3. 如果缓存的版本不可用,ASP.NET 重新创建项,使用此项,然后将它保存在Cache对象以备后用。
1、定义缓存处理私有对象
#region 私有
/// <summary>
/// 线程安全锁
/// </summary>
private static object asyncLock = new object();
/// <summary>
/// 获取状态缓存的键名
/// </summary>
/// <param name="cacheName">缓存名称</param>
/// <returns></returns>
private static string GetStateCacheKey(string cacheName)
{
return string.Format("{0}{1}", cacheName, "_Dep");
}
/// <summary>
/// 设置一个缓存的状态为 Loading 状态,并记录当前进程ID。
/// <para>一个缓存的 Loading 状态最长可以存在10分钟。</para>
/// </summary>
/// <param name="cacheName">缓存名称</param>
private static void SetCacheLoading(string cacheName)
{
HttpRuntime.Cache.Insert(GetStateCacheKey(cacheName), Thread.CurrentThread.ManagedThreadId, null, DateTime.Now.AddMinutes(10), TimeSpan.Zero);
}
/// <summary>
/// 是否忽略缓存设置操作。
/// </summary>
/// <param name="cacheName">缓存名称</param>
/// <returns>状态缓存为空或状态缓存值为当前线程ID时,返回 False,代表不忽略缓存设置操作。</returns>
private static bool IsIgnoreCacheSet(string cacheName)
{
if (HttpRuntime.Cache[GetStateCacheKey(cacheName)] == null
|| (int)HttpRuntime.Cache[GetStateCacheKey(cacheName)] == Thread.CurrentThread.ManagedThreadId)
return false;
return true;
}
#endregion
2、设置缓存的方法如下
#region 设置缓存
/// <summary>
/// 设置无过期缓存。
/// </summary>
/// <param name="cacheName">缓存的名称</param>
/// <param name="val">要缓存的对象</param>
public static void SetCache(string cacheName, object val)
{
if (val == null)
return;
if (IsIgnoreCacheSet(cacheName))
return;
HttpRuntime.Cache.Insert(cacheName, val);
CacheDependency dependency = new CacheDependency(null, new string[] { cacheName });
HttpRuntime.Cache.Insert(GetStateCacheKey(cacheName), 0, dependency);
LogHelper.Info("Sxmobi.CacheHelper",
string.Format("缓存({0})已更新,操作线程ID:{1}", cacheName, Thread.CurrentThread.ManagedThreadId), null);
}
/// <summary>
/// 设置时间过期缓存(滑动过期或绝对过期)。
/// </summary>
/// <param name="cacheName">缓存的名称</param>
/// <param name="val">要缓存的对象</param>
/// <param name="cacheTime">要缓存的时长(分钟)</param>
/// <param name="isSlidingTime">是否为滑动过期</param>
public static void SetCache(string cacheName, object val, int cacheTime, bool isSlidingTime)
{
if (val == null)
return;
if (cacheTime < 1)
return;
if (IsIgnoreCacheSet(cacheName))
return;
if (isSlidingTime)
HttpRuntime.Cache.Insert(cacheName, val, null, Cache.NoAbsoluteExpiration, new TimeSpan(0, cacheTime * 2, 0));
else
HttpRuntime.Cache.Insert(cacheName, val, null, DateTime.Now.AddMinutes(cacheTime * 2), TimeSpan.Zero);
CacheDependency dependency = new CacheDependency(null, new string[] { cacheName });
if (isSlidingTime)
HttpRuntime.Cache.Insert(GetStateCacheKey(cacheName), 0, dependency, Cache.NoAbsoluteExpiration, new TimeSpan(0, cacheTime, 0));
else
HttpRuntime.Cache.Insert(GetStateCacheKey(cacheName), 0, dependency, DateTime.Now.AddMinutes(cacheTime), TimeSpan.Zero);
LogHelper.Info("Sxmobi.CacheHelper",
string.Format("缓存({0})已更新,操作线程ID:{1}", cacheName, Thread.CurrentThread.ManagedThreadId), null);
}
/// <summary>
/// 设置文件依赖缓存。
/// </summary>
/// <param name="cacheName">缓存的名称</param>
/// <param name="val">要缓存的对象</param>
/// <param name="file">缓存依赖的文件</param>
public static void SetCache(string cacheName, object val, string file)
{
SetCache(cacheName, val, new string[] { file });
}
/// <summary>
/// 设置文件依赖缓存。
/// </summary>
/// <param name="cacheName">缓存的名称</param>
/// <param name="val">要缓存的对象</param>
/// <param name="files">缓存依赖的文件组</param>
public static void SetCache(string cacheName, object val, string[] files)
{
if (val == null)
return;
if (files.Length == 0)
return;
if (IsIgnoreCacheSet(cacheName))
return;
foreach (string file in files)
FileHelper.EnsureFile(file);
HttpRuntime.Cache.Insert(cacheName, val, null, Cache.NoAbsoluteExpiration, new TimeSpan(12, 0, 0), CacheItemPriority.High, null);
System.Web.Caching.CacheDependency dependency = new System.Web.Caching.CacheDependency(files, new string[] { cacheName });
HttpRuntime.Cache.Insert(GetStateCacheKey(cacheName), 0, dependency);
LogHelper.Info("Sxmobi.CacheHelper",
string.Format("缓存({0})已更新,操作线程ID:{1}", cacheName, Thread.CurrentThread.ManagedThreadId), null);
}
#endregion
3、异步缓存设置(当前线程并非最初检测到缓存过期的线程)
#region 异步缓存设置(当前线程并非最初检测到缓存过期的线程)
/// <summary>
/// 设置无过期缓存。(当前线程并非最初检测到缓存过期的线程)
/// </summary>
/// <param name="cacheName">缓存的名称</param>
/// <param name="val">要缓存的对象</param>
public static void SetCacheAsync(string cacheName, object val)
{
if (val == null)
return;
HttpRuntime.Cache.Insert(cacheName, val);
CacheDependency dependency = new CacheDependency(null, new string[] { cacheName });
HttpRuntime.Cache.Insert(GetStateCacheKey(cacheName), 0, dependency);
LogHelper.Info("Sxmobi.CacheHelper",
string.Format("缓存({0})已更新,操作线程ID:{1}", cacheName, Thread.CurrentThread.ManagedThreadId), null);
}
/// <summary>
/// 设置时间过期缓存(滑动过期或绝对过期)。(当前线程并非最初检测到缓存过期的线程)
/// </summary>
/// <param name="cacheName">缓存的名称</param>
/// <param name="val">要缓存的对象</param>
/// <param name="cacheTime">要缓存的时长(分钟)</param>
/// <param name="isSlidingTime">是否为滑动过期</param>
public static void SetCacheAsync(string cacheName, object val, int cacheTime, bool isSlidingTime)
{
if (val == null)
return;
if (cacheTime < 1)
return;
if (isSlidingTime)
HttpRuntime.Cache.Insert(cacheName, val, null, Cache.NoAbsoluteExpiration, new TimeSpan(0, cacheTime * 2, 0));
else
HttpRuntime.Cache.Insert(cacheName, val, null, DateTime.Now.AddMinutes(cacheTime * 2), TimeSpan.Zero);
CacheDependency dependency = new CacheDependency(null, new string[] { cacheName });
if (isSlidingTime)
HttpRuntime.Cache.Insert(GetStateCacheKey(cacheName), 0, dependency, Cache.NoAbsoluteExpiration, new TimeSpan(0, cacheTime, 0));
else
HttpRuntime.Cache.Insert(GetStateCacheKey(cacheName), 0, dependency, DateTime.Now.AddMinutes(cacheTime), TimeSpan.Zero);
LogHelper.Info("Sxmobi.CacheHelper",
string.Format("缓存({0})已更新,操作线程ID:{1}", cacheName, Thread.CurrentThread.ManagedThreadId), null);
}
/// <summary>
/// 设置文件依赖缓存。(当前线程并非最初检测到缓存过期的线程)
/// </summary>
/// <param name="cacheName">缓存的名称</param>
/// <param name="val">要缓存的对象</param>
/// <param name="file">缓存依赖的文件</param>
public static void SetCacheAsync(string cacheName, object val, string file)
{
SetCacheAsync(cacheName, val, new string[] { file });
}
/// <summary>
/// 设置文件依赖缓存。(当前线程并非最初检测到缓存过期的线程)
/// </summary>
/// <param name="cacheName">缓存的名称</param>
/// <param name="val">要缓存的对象</param>
/// <param name="files">缓存依赖的文件组</param>
public static void SetCacheAsync(string cacheName, object val, string[] files)
{
if (val == null)
return;
if (files.Length == 0)
return;
foreach (string file in files)
FileHelper.EnsureFile(file);
HttpRuntime.Cache.Insert(cacheName, val, null, Cache.NoAbsoluteExpiration, new TimeSpan(12, 0, 0), CacheItemPriority.High, null);
System.Web.Caching.CacheDependency dependency = new System.Web.Caching.CacheDependency(files, new string[] { cacheName });
HttpRuntime.Cache.Insert(GetStateCacheKey(cacheName), 0, dependency);
LogHelper.Info("Sxmobi.CacheHelper",
string.Format("缓存({0})已更新,操作线程ID:{1}", cacheName, Thread.CurrentThread.ManagedThreadId), null);
}
#endregion
4、缓存相关方法
#region 缓存相关方法
/// <summary>
/// 删除缓存。
/// </summary>
/// <param name="cacheName">缓存的名称</param>
/// <param name="isDeleteSrcCache">是否删除源值缓存(False=仅删除状态缓存)</param>
public static void DelCache(string cacheName, bool isDeleteSrcCache)
{
HttpRuntime.Cache.Remove(GetStateCacheKey(cacheName));
if (isDeleteSrcCache)
HttpRuntime.Cache.Remove(cacheName);
LogHelper.Info("Sxmobi.CacheHelper",
string.Format("缓存({0})已删除,操作线程ID:{1},是否删除对象缓存:{2}", cacheName, Thread.CurrentThread.ManagedThreadId, isDeleteSrcCache), null);
}
/// <summary>
/// 获取缓存对象。
/// <para>如果返回的缓存对象不为 Null,则表示缓存可用,</para>
/// <para>否则,请调用者自数据库或其它位置获取对象,并设置该缓存。</para>
/// <para></para>
/// <para>应用实例:</para>
/// <para>string cacheName = "UserList";</para>
/// <para>DataTable dtUserList = CacheHelper.GetCache(cacheName) as DataTable;</para>
/// <para>if (dtUserList == null)</para>
/// <para>{</para>
/// <para> dtUserList = GetUserListByDB();</para>
/// <para> // 不会造成循环失效,因为 SetCache 方法仅允许第一个 GetCache 时返回空的进程设置缓存。</para>
/// <para> CacheHelper.SetCache(cacheName, dtUserList);</para>
/// <para>}</para>
/// <para>return dtUserList;</para>
/// </summary>
/// <param name="cacheName">缓存的名称</param>
/// <returns></returns>
public static object GetCache(string cacheName)
{
if (HttpRuntime.Cache[GetStateCacheKey(cacheName)] == null)
{
lock (asyncLock)
{
if (HttpRuntime.Cache[GetStateCacheKey(cacheName)] == null)
{
// 缓存过期或不存在
SetCacheLoading(cacheName);
LogHelper.Info("Sxmobi.CacheHelper",
string.Format("缓存({0})已过期,发现线程ID:{1}", cacheName, Thread.CurrentThread.ManagedThreadId), null);
return null;
}
}
}
return HttpRuntime.Cache[cacheName];
}
#endregion
5、最后,给一个缓存应用实例
public Dictionary<string, int> GetPlatforms()
{
string cacheName = "Platforms";
Dictionary<string, int> dic = CacheHelper.GetCache(cacheName) as Dictionary<string, int>;
if (dic == null)
{
dic = new Dictionary<string, int>();
DataTable dt = GetPlatformsData();
if (dt == null)
{
LogHelper.Error("BaseBLL.cs", "数据库获取平台名称与平台ID键值表失败", null);
return dic;
}
string key;
foreach (DataRow dr in dt.Rows)
{
key = dr["platName"].ToString().ToUpper();
if (!dic.ContainsKey(key))
{
dic.Add(key, Tools.GetInt(dr["platformId"].ToString(), 0));
}
}
CacheHelper.SetCache(cacheName, dic);
}
return dic;
}
简单解析一下上面的方法:
1、根据cacheName = "Platforms"获取缓存;
2、如果获取缓存失败,则从数据库重新取数据DataTable dt = GetPlatformsData();
重新设置缓存: CacheHelper.SetCache(cacheName, dic);
3、如果获取缓存成功,直接返回缓存结果return dic;