[C#]関数のキャッシュを容易にする方法

4203 ワード

構想
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
{
	/// 
	///  MemoryCache         。     db redis   ,              。
	///   :var preData = CacheHelper.WithCache("userstock_GetUSPreDataTradingCode_" + stockCode, ()=>Redis.QuoteData.GetUSPreDataTradingCode(stockCode), 5000);
	/// 
	public static class CacheHelper
	{
		private static MemoryCache mc = new MemoryCache(Guid.NewGuid().ToString());

		/// 
		///  MemoryCache  getData   。Func     ()=>getData(a,b,c)
		/// 
		/// 
		///   key    
		/// 
		/// 
		/// 
		/// 
		public static T WithCache(string key, Func 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;
			/// 
			///       ,          
			/// 
			public bool isRefreshing;

			/// 
			///         
			/// 
			/// 
			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 dicCacheInfo = new ConcurrentDictionary();

		/// 
		///  MemoryCache  getData   。Func     ()=>getData(a,b,c)。
		///          ,             ,     Func           。
		/// 
		/// 
		///   key    
		/// 
		/// 
		/// 
		/// 
		public static T WithCacheRefresh(string key, Func 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;
			}
		}

		/// 
		/// 
		/// 
		/// 
		/// 
		/// 
		/// 
		private static void CheckRefreshCache(string key, Func 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;
		}
	}
}