[C#] 如何方便的給函數加緩存

思路

利用MemoryCache對“特定函數的特定輸入”的執行結果做緩存,那麼可以節省大量對db和redis的訪問。

從外部對函數的執行結果做緩存,相比於修改函數在函數內部做緩存,更加松耦合,沒有侵入性。

 

實現

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Caching;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace WSQ.Common
{
	/// <summary>
	/// 用MemoryCache緩存函數的執行結果。可以減少對db和redis的讀取,也可以減少任何其他函數的執行。
	/// 用例:var preData = CacheHelper.WithCache("userstock_GetUSPreDataTradingCode_" + stockCode, ()=>Redis.QuoteData.GetUSPreDataTradingCode(stockCode), 5000);
	/// </summary>
	public static class CacheHelper
	{
		private static MemoryCache mc = new MemoryCache(Guid.NewGuid().ToString());

		/// <summary>
		/// 用MemoryCache緩存getData的結果。Func部分可以 ()=>getData(a,b,c)
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="key">注意key要夠獨特</param>
		/// <param name="getData"></param>
		/// <param name="arg"></param>
		/// <param name="cacheMilli"></param>
		/// <returns></returns>
		public static T WithCache<T>(string key, Func<T> getData, double cacheMilli = 1000) where T : new()
		{
			object obj = mc.Get(key);
			if (obj != null)
			{
				return (T)obj;
			}
			else
			{
				T dd = getData();
				if (dd == null)
				{
					dd = new T();
				}
				mc.Set(key, dd, DateTime.Now.AddMilliseconds(cacheMilli));
				return dd;
			}
		}

		private class CacheInfo
		{
			public string key;
			public DateTime lastCacheTime;
			public double cacheMilli;
			/// <summary>
			/// 控制併發程度,不需要嚴格的線程安全
			/// </summary>
			public bool isRefreshing;

			/// <summary>
			/// 是否需要提前刷新
			/// </summary>
			/// <returns></returns>
			public bool NeedPredicateRefresh()
			{
				var milliExist = (DateTime.Now - this.lastCacheTime).TotalMilliseconds;

				//正在提前刷新的別再提前刷新。
				//緩存太短的數據沒必要提前刷新。
				//快過期了才提前刷新。
				bool needRefresh = !isRefreshing && this.cacheMilli > 500 && milliExist > (this.cacheMilli * 0.8);
				return needRefresh;
			}
		}
		private static ConcurrentDictionary<string, CacheInfo> dicCacheInfo = new ConcurrentDictionary<string, CacheInfo>();

		/// <summary>
		/// 用MemoryCache緩存getData的結果。Func部分可以 ()=>getData(a,b,c)。
		/// 在緩存快過期的時候,按需啓動線程去提前刷新緩存,以儘量避免Func太慢導致的偶發響應變慢。
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="key">注意key要夠獨特</param>
		/// <param name="getData"></param>
		/// <param name="arg"></param>
		/// <param name="cacheMilli"></param>
		/// <returns></returns>
		public static T WithCacheRefresh<T>(string key, Func<T> getData, double cacheMilli = 1000) where T : new()
		{
			var info = dicCacheInfo.GetOrAdd(key, new CacheInfo()
			{
				key = key,
				lastCacheTime = new DateTime(),
				cacheMilli = cacheMilli,
				isRefreshing = false
			});

			object obj = mc.Get(key);
			if (obj != null)
			{
				//cache存在,看看要不要提前刷新
				if (info.NeedPredicateRefresh())
				{
					//立刻設置一下isRefreshing,避免起太多線程
					info.isRefreshing = true;

					Thread th = new Thread(new ThreadStart(() => CheckRefreshCache(key, getData, cacheMilli)));
					th.Name = "WithCacheConcurrent_refresh";
					th.IsBackground = true;
					th.Start();
				}

				return (T)obj;
			}
			else
			{
				T dd = getData();
				if (dd == null)
				{
					dd = new T();
				}
				mc.Set(key, dd, DateTime.Now.AddMilliseconds(cacheMilli));

				info.lastCacheTime = DateTime.Now;
				info.cacheMilli = cacheMilli;

				return dd;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="key"></param>
		/// <param name="getData"></param>
		/// <param name="cacheMilli"></param>
		private static void CheckRefreshCache<T>(string key, Func<T> getData, double cacheMilli) where T : new()
		{
			dicCacheInfo.TryGetValue(key, out CacheInfo info);
			if (info == null)
			{
				return;
			}

			try
			{
				T dd = getData();
				if (dd == null)
				{
					dd = new T();
				}
				mc.Set(key, dd, DateTime.Now.AddMilliseconds(cacheMilli));

				info.lastCacheTime = DateTime.Now;
				info.cacheMilli = cacheMilli;
			}
			catch (Exception)
			{
			}
			info.isRefreshing = false;
		}
	}
}

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章