C# 內存緩存工具類 MemoryCacheUtil

using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using System.Timers; namespace Utils { /// <summary> /// 緩存 /// 緩存數據存儲在內存中 /// 適用於CS項目,BS項目慎用 /// </summary> public static class MemoryCacheUtil { #region 變量 /// <summary> /// 內存緩存 /// </summary> private static ConcurrentDictionary<string, CacheData> _cacheDict = new ConcurrentDictionary<string, CacheData>(); /// <summary> /// 對不同的鍵提供不同的鎖,用於讀緩存 /// </summary> private static ConcurrentDictionary<string, string> _dictLocksForReadCache = new ConcurrentDictionary<string, string>(); /// <summary> /// 過期緩存檢測Timer /// </summary> private static Timer _timerCheckCache; #endregion #region 靜態構造函數 static MemoryCacheUtil() { _timerCheckCache = new Timer(); _timerCheckCache.Interval = 60 * 1000; _timerCheckCache.Elapsed += _timerCheckCache_Elapsed; _timerCheckCache.Start(); } #endregion #region 獲取並緩存數據 /// <summary> /// 獲取並緩存數據 /// 高並發的情況建議使用此重載函數,防止重復寫入內存緩存 /// </summary> /// <param name="cacheKey">鍵</param> /// <param name="func">在此方法中初始化數據</param> /// <param name="expirationSeconds">緩存過期時間(秒),0表示永不過期</param> /// <param name="refreshCache">立即刷新緩存</param> public static T TryGetValue<T>(string cacheKey, Func<T> func, int expirationSeconds = 0, bool refreshCache = false) { lock (_dictLocksForReadCache.GetOrAdd(cacheKey, cacheKey)) { object cacheValue = MemoryCacheUtil.GetValue(cacheKey); if (cacheValue != null && !refreshCache) { return (T)cacheValue; } else { T value = func(); MemoryCacheUtil.SetValue(cacheKey, value, expirationSeconds); return value; } } } #endregion #region SetValue 保存鍵值對 /// <summary> /// 保存鍵值對 /// </summary> /// <param name="key">緩存鍵</param> /// <param name="value">值</param> /// <param name="expirationSeconds">過期時間(秒),0表示永不過期</param> internal static void SetValue(string key, object value, int expirationSeconds = 0) { try { CacheData data = new CacheData(key, value); data.updateTime = DateTime.Now; data.expirationSeconds = expirationSeconds; CacheData temp; _cacheDict.TryRemove(key, out temp); _cacheDict.TryAdd(key, data); } catch (Exception ex) { LogUtil.Error(ex, "MemoryCacheUtil寫緩存錯誤"); } } #endregion #region GetValue 獲取鍵值對 /// <summary> /// 獲取鍵值對 /// </summary> internal static object GetValue(string key) { try { CacheData data; if (_cacheDict.TryGetValue(key, out data)) { if (data.expirationSeconds > 0 && DateTime.Now.Subtract(data.updateTime).TotalSeconds > data.expirationSeconds) { CacheData temp; _cacheDict.TryRemove(key, out temp); return null; } return data.value; } return null; } catch (Exception ex) { LogUtil.Error(ex, "MemoryCacheUtil讀緩存錯誤"); return null; } } #endregion #region Delete 刪除緩存 /// <summary> /// 刪除緩存 /// </summary> internal static void Delete(string key) { CacheData temp; _cacheDict.TryRemove(key, out temp); } #endregion #region DeleteAll 刪除全部緩存 /// <summary> /// 刪除全部緩存 /// </summary> internal static void DeleteAll() { _cacheDict.Clear(); } #endregion #region 過期緩存檢測 /// <summary> /// 過期緩存檢測 /// </summary> private static void _timerCheckCache_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { Task.Run(() => { try { foreach (string cacheKey in _cacheDict.Keys.ToList()) { CacheData data; if (_cacheDict.TryGetValue(cacheKey, out data)) { if (data.expirationSeconds > 0 && DateTime.Now.Subtract(data.updateTime).TotalSeconds > data.expirationSeconds) { CacheData temp; string strTemp; _cacheDict.TryRemove(cacheKey, out temp); _dictLocksForReadCache.TryRemove(cacheKey, out strTemp); } } } } catch (Exception ex) { LogUtil.Error(ex, "過期緩存檢測出錯"); } }); } #endregion } }
為什么BS項目慎用?因為IIS會回收進程,所以需要注意一下。
為什么過期緩存檢測遍歷代碼是foreach (string cacheKey in _cacheDict.Keys.ToList()),要使用ToList()?_cacheDict.Keys不是線程安全的,防止並發異常。
為什么加鎖的代碼是lock (_dictLocksForReadCache.GetOrAdd(cacheKey, cacheKey))?為了支持多線程並發,防止重復進入func函數。
CacheData類:

/// <summary> /// 緩存數據 /// </summary> [Serializable] public class CacheData { /// <summary> /// 鍵 /// </summary> public string key { get; set; } /// <summary> /// 值 /// </summary> public object value { get; set; } /// <summary> /// 緩存更新時間 /// </summary> public DateTime updateTime { get; set; } /// <summary> /// 過期時間(秒),0表示永不過期 /// </summary> public int expirationSeconds { get; set; } /// <summary> /// 緩存數據 /// </summary> /// <param name="key">緩存鍵</param> /// <param name="value">值</param> public CacheData(string key, object value) { this.key = key; this.value = value; } }
如何使用:

private void button2_Click(object sender, EventArgs e) { List<string> list = MemoryCacheUtil.TryGetValue<List<string>>("cacheKey001", () => { return QueryData(); }); } /// <summary> /// 模擬從數據庫查詢數據 /// </summary> private List<string> QueryData() { List<string> result = new List<string>(); for (int i = 0; i < 10; i++) { result.Add(i.ToString()); } return result; }
多線程並發測試:

private void TestMemoryCache() { Log("開始"); for (int i = 0; i < 5; i++) { Task.Run(() => { string str1 = MemoryCacheUtil.TryGetValue<string>("1", () => { Thread.Sleep(2000); Log("取數據1"); return "1"; }); Log(str1); }); Task.Run(() => { string str2 = MemoryCacheUtil.TryGetValue<string>("2", () => { Thread.Sleep(2000); Log("取數據2"); return "2"; }); Log(str2); }); Task.Run(() => { string str3 = MemoryCacheUtil.TryGetValue<string>("3", () => { Thread.Sleep(2000); Log("取數據3"); return "3"; }); Log(str3); }); } }