heinrich23

C# easy Singleton Cache Pattern

Apr 27th, 2025 (edited)
526
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 32.64 KB | Source Code | 0 0
  1. using Newtonsoft.Json;
  2. using StackExchange.Redis;
  3. using System.Configuration;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Linq;
  7. using System.Text;
  8. using System.Threading.Tasks;
  9. using System.Collections.Concurrent;
  10. using System.Threading;
  11.  
  12. namespace ConsoleAppCacheTest
  13. {
  14.  
  15.     /// <summary>
  16.     /// CacheTypVal any cached value.
  17.     /// Use default empty ctor <see cref="CacheTypVal()"/> and
  18.     /// <see cref="SetValue{T}(T)"/> to set the cached value;
  19.     /// setting cache value via <see cref="CacheTypVal(object, Type)"/> ctor is obsolete.
  20.     /// Use <see cref="GetValue{T}"/> to get the cached value
  21.     /// </summary>
  22.     [Serializable]
  23.     public class CacheTypVal
  24.     {
  25.  
  26.         public object? _Value { get; protected internal set; }
  27.         public Type? _Type { get; protected internal set; }
  28.  
  29.         /// <summary>
  30.         /// Empty default ctor
  31.         /// </summary>
  32.         public CacheTypVal()
  33.         {
  34.             _Type = null;
  35.             _Value = null;
  36.         }
  37.  
  38.         /// <summary>
  39.         /// Obsolete ctor, please use default empty ctor <see cref="CacheTypVal()"/>
  40.         /// and then <see cref="SetValue{T}(T)"/> to set a cached value instead.
  41.         /// </summary>
  42.         /// <param name="ovalue"><see cref="object" /> ovalue</param>
  43.         /// <param name="atype"><see cref="Type"/> atype</param>
  44.         [Obsolete("Don't use ctor CacheTypeValue(object, Type) to set a cache value, use SetValue<T>(T tvalue) instead.", false)]
  45.         public CacheTypVal(object ovalue, Type atype)
  46.         {
  47.             _Type = atype;
  48.             _Value = ovalue;
  49.         }
  50.  
  51.         /// <summary>
  52.         /// gets the <see cref="Type"/> of generic cached value
  53.         /// </summary>
  54.         /// <returns><see cref="Type"/> of generic value or null if cached value is <see cref="null"/></returns>
  55.         public Type? GetType() => _Type;
  56.  
  57.  
  58.         /// <summary>
  59.         /// Get a value from cache
  60.         /// </summary>
  61.         /// <typeparam name="T">generic type of value passed by typeparameter</typeparam>
  62.         /// <returns>generic T value</returns>
  63.         /// <exception cref="InvalidOperationException">thrown, when cached value isn't of typeof(T)</exception>
  64.         public T? GetValue<T>()
  65.         {
  66.             T? tvalue;
  67.             if (typeof(T) == _Type || typeof(T).IsSubclassOf(_Type))
  68.                 tvalue = (T?)_Value;
  69.             else
  70.                 throw new InvalidOperationException($"typeof(T) = {typeof(T)} while _type = {_Type}");
  71.  
  72.             return tvalue ?? default(T);
  73.         }
  74.  
  75.         /// <summary>
  76.         /// Sets a generic cached value
  77.         /// </summary>
  78.         /// <typeparam name="T">generic type of value passed by typeparameter</typeparam>
  79.         /// <param name="tvalue">generic value to set cached</param>
  80.         public void SetValue<T>(T tvalue)
  81.         {
  82.             _Type = typeof(T);
  83.             _Value = (object)tvalue;
  84.         }
  85.  
  86.     }
  87.  
  88.  
  89.     /// <summary>
  90.     /// MemCache an application cache implemented saved in memory only at runtime
  91.     /// derive from <see cref="MemCache"/> and implement your own cache by implementing a new variant
  92.     /// </summary>
  93.     public abstract class MemCache
  94.     {
  95.  
  96.         public const string APP_CONCURRENT_DICT = "APP_CONCURRENT_DICT";
  97.         protected internal readonly Lock _lock = new Lock();
  98.  
  99.         protected internal static readonly Lazy<MemCache> _instance;
  100.         public static MemCache CacheDict => _instance.Value;
  101.  
  102.         public static readonly string CacheVariant = "MemCache";
  103.  
  104.         /// <summary>
  105.         /// private <see cref="ConcurrentDictionary{string, CacheTypeValue}"/>
  106.         /// </summary>
  107.         protected internal static ConcurrentDictionary<string, CacheTypVal> _appCache = new ConcurrentDictionary<string, CacheTypVal>();
  108.  
  109.         /// <summary>
  110.         /// public property get accessor for <see cref="_appCache"/> stored in <see cref="AppDomain.CurrentDomain"/>
  111.         /// </summary>
  112.         protected virtual ConcurrentDictionary<string, CacheTypVal> AppCache
  113.         {
  114.             get
  115.             {
  116.                 // _appCache =  (ConcurrentDictionary<string, CacheTypVal>) get it where to get it
  117.                 if (_appCache == null)
  118.                 {
  119.                     _appCache = new ConcurrentDictionary<string, CacheTypVal>();
  120.                     // where to set it _appCache
  121.                 }
  122.                 return _appCache;
  123.             }
  124.             set
  125.             {
  126.                 if (value != null && value.Count > 0)
  127.                 {
  128.                     _appCache = value;
  129.                     // if (_appCache != null && _appCache.Count > 0)
  130.                     //      set it where to set it _appCache
  131.                 }
  132.             }
  133.         }
  134.  
  135.         public object? this[string ckey]
  136.         {
  137.             get => (AppCache.ContainsKey(ckey) && AppCache.TryGetValue(ckey, out CacheTypVal? cvalue)) ? cvalue._Value : null;
  138.             set
  139.             {
  140.                 object? ovalue = value;
  141.                 Type? otype = value?.GetType();
  142.                 if (AppCache.ContainsKey(ckey) && AppCache.TryGetValue(ckey, out CacheTypVal oldValue))
  143.                     _appCache.TryRemove(ckey, out oldValue);
  144.  
  145.                 _appCache.TryAdd(ckey, new CacheTypVal(ovalue, otype));
  146.                 AppCache = _appCache;
  147.             }
  148.         }
  149.  
  150.         /// <summary>
  151.         /// Get all keys from <see cref="AppCache"/> which is implemented as a <see cref="ConcurrentDictionary{string, CacheTypVal}"/>
  152.         /// </summary>
  153.         public virtual string[] AllKeys { get => AppCache.Keys.ToArray(); }
  154.  
  155.         /// <summary>
  156.         /// static ctor
  157.         /// </summary>
  158.         static MemCache()
  159.         {
  160.             string persistMsgIn = "JsonFile";
  161.  
  162.             if (ConfigurationManager.AppSettings["PersistMsgIn"] != null)
  163.             {
  164.                 persistMsgIn = (string)ConfigurationManager.AppSettings["PersistMsgIn"];
  165.             }
  166.  
  167.             switch (persistMsgIn)
  168.             {
  169.                 case "JsonFile":
  170.                     CacheVariant = "JsonFile";
  171.                     _instance = new Lazy<MemCache>(() => new JsonCache());
  172.                     break;
  173.                 case "Redis":
  174.                     // TODO: Redis
  175.                     CacheVariant = "Redis";
  176.                     _instance = new Lazy<MemCache>(() => new RedIsCache());
  177.                     break;
  178.                 case "ApplicationState":
  179.                 case "AppDomain":
  180.                 default:
  181.                     CacheVariant = "AppDomain";
  182.                     _instance = new Lazy<MemCache>(() => new AppCurrentDomainCache());
  183.                     break;
  184.             }
  185.         }
  186.  
  187.  
  188.         /// <summary>
  189.         /// Static constructor
  190.         /// </summary>
  191.         public MemCache()
  192.         {
  193.             _appCache = new ConcurrentDictionary<string, CacheTypVal>();
  194.         }
  195.  
  196.         /// <summary>
  197.         /// Gets a value from <see cref="ConcurrentDictionary<string, CacheTypVal>"/> stored <see cref="System.AppDomain.CurrentDomain"/>
  198.         /// </summary>
  199.         /// <typeparam name="T">generic type of cached value</typeparam>
  200.         /// <param name="ckey">cache key</param>
  201.         /// <returns>generic cached value stored at key</returns>
  202.         public virtual T GetValue<T>(string ckey)
  203.         {
  204.             T tvalue = (AppCache.ContainsKey(ckey) && AppCache.TryGetValue(ckey, out var cvalue)) ? cvalue.GetValue<T>() : default(T);
  205.  
  206.             return tvalue;
  207.         }
  208.  
  209.         /// <summary>
  210.         /// Sets a generic value to <see cref="ConcurrentDictionary<string, CacheTypVal>"/> stored <see cref="System.AppDomain.CurrentDomain"/>
  211.         /// </summary>
  212.         /// <typeparam name="T">generic type of cached value</typeparam>
  213.         /// <param name="ckey">cache key</param>
  214.         /// <param name="cvalue">generic value to stored at key in cache</param>
  215.         /// <returns>true, if add or updated succeeded, otherwise false</returns>
  216.         public virtual bool SetValue<T>(string ckey, T tvalue)
  217.         {
  218.             bool addedOrUpdated = false;
  219.  
  220.             if (string.IsNullOrEmpty(ckey) || tvalue == null)
  221.                 return addedOrUpdated;
  222.  
  223.             CacheTypVal cvalue = new CacheTypVal();
  224.             cvalue.SetValue<T>(tvalue);
  225.  
  226.             if (!AppCache.ContainsKey(ckey))
  227.                 addedOrUpdated = AppCache.TryAdd(ckey, cvalue);
  228.             else if (AppCache.TryGetValue(ckey, out CacheTypVal oldValue))
  229.                 addedOrUpdated = _appCache.TryUpdate(ckey, cvalue, oldValue);
  230.  
  231.             // MAYBE SHORTER BUT NOBODY CAN QUICK READ AND UNDERSTAND THIS
  232.             // addedOrUpdated = (!AppCache.ContainsKey(ckey)) ? AppCache.TryAdd(ckey, cvalue) :
  233.             //    (AppCache.TryGetValue(ckey, out CacheTypVal oldValue)) ? _appCache.TryUpdate(ckey, cvalue, oldValue) : false;
  234.  
  235.             if (addedOrUpdated)
  236.                 AppCache = _appCache;  // saves the modified ConcurrentDictionary{string, CacheTypVal} back to AppDomain
  237.  
  238.             return addedOrUpdated;
  239.         }
  240.  
  241.         /// <summary>
  242.         /// Looks, if  <see cref="ConcurrentDictionary{string, CacheTypVal}"/>  contains the key
  243.         /// </summary>
  244.         /// <param name="ckey">lookup key</param>
  245.         /// <returns>true, if ckey is not null or empty and <see cref="AppCache"/> contains ckey, otherwise false</returns>
  246.         public virtual bool ContainsKey(string ckey)
  247.         {
  248.             return (!string.IsNullOrEmpty(ckey) && AppCache.ContainsKey(ckey));
  249.         }
  250.  
  251.         /// <summary>
  252.         /// RemoveKey removes a key value pair from <see cref="AppCache"/>
  253.         /// </summary>
  254.         /// <param name="ckey">key to remove</param>
  255.         /// <returns>true, if key value pair was successfully removed or <see cref="AppCache"/> doesn't contain anymore ckey;
  256.         /// false if ckey is <see cref="null"/> or <see cref="string.Empty"/> or removing ckey from <see cref="ConcurrentDictionary{string, CacheTypVal}"/> failed.</returns>
  257.         public virtual bool RemoveKey(string ckey)
  258.         {
  259.             bool success = false;
  260.             if (string.IsNullOrEmpty(ckey))
  261.                 return success;
  262.  
  263.             if ((success = !AppCache.ContainsKey(ckey)) == false)
  264.                 if ((success = AppCache.TryRemove(ckey, out CacheTypVal cvalue)) == true)
  265.                     AppCache = _appCache; // saves the modified ConcurrentDictionary{string, CacheTypVal} back to AppDomain
  266.  
  267.             return success;
  268.         }
  269.  
  270.     }
  271.  
  272.  
  273.  
  274.     /// <summary>
  275.     /// JsonCache an application cache implemented with <see cref="ConcurrentDictionary{string, CacheTypVal}"/> serialized with json    
  276.     /// </summary>
  277.     public class JsonCache : MemCache
  278.     {
  279.  
  280.         //protected internal static readonly Lazy<MemCache> _instance = new Lazy<MemCache>(() => new JsonCache());
  281.         //public static MemCache CacheDict => _instance.Value;
  282.  
  283.         const int INIT_SEM_COUNT = 1;
  284.         const int MAX_SEM_COUNT = 1;
  285.         const string JSON_APPCACHE_FILE = "AppCache.json";
  286.         readonly static string JsonFullDirPath = Path.Combine(Environment.GetEnvironmentVariable("LOCALAPPDATA"), "TEMP");
  287.         readonly static string JsonFullFilePath = Path.Combine(JsonFullDirPath, JSON_APPCACHE_FILE);
  288.  
  289.         protected static SemaphoreSlim ReadWriteSemaphore = new SemaphoreSlim(INIT_SEM_COUNT, MAX_SEM_COUNT);
  290.  
  291.         protected static JsonSerializerSettings JsonSettings = new JsonSerializerSettings()
  292.         {
  293.             Formatting = Formatting.Indented,
  294.             MaxDepth = 16,
  295.             // NullValueHandling = NullValueHandling.Include,
  296.             MissingMemberHandling = MissingMemberHandling.Ignore,
  297.             ObjectCreationHandling = ObjectCreationHandling.Auto,
  298.             DateFormatHandling = DateFormatHandling.IsoDateFormat,
  299.             DateParseHandling = DateParseHandling.DateTime,
  300.             PreserveReferencesHandling = PreserveReferencesHandling.All,
  301.             ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor
  302.         };
  303.  
  304.         /// <summary>
  305.         /// public property get accessor for <see cref="_appCache"/> stored in <see cref="AppDomain.CurrentDomain"/>
  306.         /// </summary>
  307.         protected override ConcurrentDictionary<string, CacheTypVal> AppCache
  308.         {
  309.             get
  310.             {
  311.                 int semCnt = 0;
  312.                 try
  313.                 {
  314.                     ReadWriteSemaphore.Wait(64);
  315.                     // if (mutex.WaitOne(250, false))
  316.                     if (_appCache == null || _appCache.Count == 0)
  317.                     {
  318.                         lock (_lock)
  319.                         {
  320.                             if (!Directory.Exists(JsonFullDirPath))
  321.                                 Directory.CreateDirectory(JsonFullDirPath);
  322.  
  323.                             string jsonSerializedAppDict = (System.IO.File.Exists(JsonFullFilePath)) ? System.IO.File.ReadAllText(JsonFullFilePath) : "";
  324.                             if (!string.IsNullOrEmpty(jsonSerializedAppDict))
  325.                                 _appCache = (ConcurrentDictionary<string, CacheTypVal>)JsonConvert.DeserializeObject<ConcurrentDictionary<string, CacheTypVal>>(jsonSerializedAppDict);
  326.                         }
  327.                         if (_appCache == null || _appCache.Count == 0)
  328.                             _appCache = new ConcurrentDictionary<string, CacheTypVal>();
  329.                     }
  330.                 }
  331.                 catch (Exception exGetRead)
  332.                 {
  333.                     Console.WriteLine($"Exception {exGetRead.GetType()}: {exGetRead.Message} \r\n\t{exGetRead}");
  334.                 }
  335.                 finally
  336.                 {
  337.                     if (ReadWriteSemaphore.CurrentCount > 0)
  338.                         semCnt = ReadWriteSemaphore.Release();
  339.                     // mutex.ReleaseMutex();
  340.                 }
  341.                 return _appCache;
  342.             }
  343.             set
  344.             {
  345.                 int semCnt = 0;
  346.                 try
  347.                 {
  348.                     semCnt = ReadWriteSemaphore.CurrentCount;
  349.                     ReadWriteSemaphore.Wait(64);
  350.  
  351.                     string jsonDeserializedAppDict = "";
  352.                     if (value != null && value.Count > 0)
  353.                     {
  354.                         // if (mutex.WaitOne(250, false))
  355.                         lock (_lock)
  356.                         {
  357.                             _appCache = value;
  358.  
  359.                             // set it, where to set it _appCache
  360.                             jsonDeserializedAppDict = JsonConvert.SerializeObject(_appCache, Formatting.Indented, JsonSettings);
  361.                             System.IO.File.WriteAllText(JsonFullFilePath, jsonDeserializedAppDict, Encoding.UTF8);
  362.                         }
  363.                     }
  364.                 }
  365.                 catch (Exception exSetWrite)
  366.                 {
  367.                     Console.WriteLine($"Exception {exSetWrite.GetType()}: {exSetWrite.Message} \r\n\t{exSetWrite}");
  368.                 }
  369.                 finally
  370.                 {
  371.                     if (ReadWriteSemaphore.CurrentCount > 0)
  372.                         semCnt = ReadWriteSemaphore.Release();
  373.                 }
  374.             }
  375.         }
  376.     }
  377.  
  378.  
  379.     /// <summary>
  380.     /// AppCurrentDomainCache an application cache implemented with a <see cref="ConcurrentDictionary{string, CacheTypVal}"/>
  381.     /// </summary>
  382.     public class AppCurrentDomainCache : MemCache
  383.     {
  384.  
  385.         /// <summary>
  386.         /// public property get accessor for <see cref="_appCache"/> stored in <see cref="AppDomain.CurrentDomain"/>
  387.         /// </summary>
  388.         protected override ConcurrentDictionary<string, CacheTypVal> AppCache
  389.         {
  390.             get
  391.             {
  392.                 _appCache = (ConcurrentDictionary<string, CacheTypVal>)AppDomain.CurrentDomain.GetData(APP_CONCURRENT_DICT);
  393.                 if (_appCache == null)
  394.                 {
  395.                     lock (_lock)
  396.                     {
  397.                         _appCache = new ConcurrentDictionary<string, CacheTypVal>();
  398.                         AppDomain.CurrentDomain.SetData(APP_CONCURRENT_DICT, _appCache);
  399.                     }
  400.                 }
  401.  
  402.                 return _appCache;
  403.             }
  404.             set
  405.             {
  406.                 if (value != null && value.Count > 0)
  407.                 {
  408.                     lock (_lock)
  409.                     {
  410.                         _appCache = value;
  411.                         AppDomain.CurrentDomain.SetData(APP_CONCURRENT_DICT, _appCache);
  412.                     }
  413.                 }
  414.             }
  415.         }
  416.  
  417.         public AppCurrentDomainCache()
  418.         {
  419.             if (AppCache == null) ;
  420.         }
  421.     }
  422.  
  423.  
  424.     /// <summary>
  425.     /// RedisCache AWS elastic valkey cache singelton connector
  426.     /// </summary>
  427.     public class RedIsCache : MemCache
  428.     {
  429.  
  430.         const string VALKEY_CACHE_HOST_PORT = "cqrcachecqrxseu-53g0xw.serverless.eus2.cache.amazonaws.com:6379";
  431.         const string VALKEY_CACHE_APP_KEY = "RedisValkeyCache";
  432.         const string ALL_KEYS = "AllKeys";
  433.  
  434.  
  435.         ConnectionMultiplexer connMux;
  436.         ConfigurationOptions options;
  437.         string endpoint = "cqrcachecqrxseu-53g0xw.serverless.eus2.cache.amazonaws.com:6379";
  438.         StackExchange.Redis.IDatabase db;
  439.  
  440.         public static MemCache ValKey => _instance.Value;
  441.  
  442.         private static HashSet<string> _allKeys = new HashSet<string>();
  443.         public override string[] AllKeys { get => GetAllKeys().ToArray(); }
  444.  
  445.         private static string _endPoint = VALKEY_CACHE_HOST_PORT;
  446.         public static string EndPoint
  447.         {
  448.             get
  449.             {
  450.                 if (ConfigurationManager.AppSettings != null && ConfigurationManager.AppSettings[VALKEY_CACHE_APP_KEY] != null)
  451.                     _endPoint = (string)ConfigurationManager.AppSettings[VALKEY_CACHE_APP_KEY];
  452.                 if (string.IsNullOrEmpty(_endPoint))
  453.                     _endPoint = VALKEY_CACHE_HOST_PORT; // back to default
  454.                 return _endPoint;
  455.             }
  456.         }
  457.  
  458.         public static StackExchange.Redis.IDatabase Db
  459.         {
  460.             get
  461.             {
  462.                 if (((RedIsCache)(_instance.Value)).db == null)
  463.                     ((RedIsCache)(_instance.Value)).db = ConnMux.GetDatabase();
  464.  
  465.                 return ((RedIsCache)(_instance.Value)).db;
  466.             }
  467.         }
  468.  
  469.         public static StackExchange.Redis.ConnectionMultiplexer ConnMux
  470.         {
  471.             get
  472.             {
  473.                 if (((RedIsCache)(_instance.Value)).connMux == null)
  474.                 {
  475.                     if (((RedIsCache)(_instance.Value)).options == null)
  476.                         ((RedIsCache)(_instance.Value)).options = new ConfigurationOptions
  477.                         {
  478.                             EndPoints = { EndPoint },
  479.                             Ssl = true
  480.                         };
  481.                     ((RedIsCache)(_instance.Value)).connMux = ConnectionMultiplexer.Connect(((RedIsCache)(_instance.Value)).options);
  482.                 }
  483.                 return ((RedIsCache)(_instance.Value)).connMux;
  484.             }
  485.         }
  486.  
  487.  
  488.         /// <summary>
  489.         /// default parameterless constructor for RedisCacheValKey cache singleton
  490.         /// </summary>
  491.         public RedIsCache()
  492.         {
  493.             endpoint = VALKEY_CACHE_HOST_PORT; // "cqrcachecqrxseu-53g0xw.serverless.eus2.cache.amazonaws.com:6379";
  494.             if (ConfigurationManager.AppSettings != null && ConfigurationManager.AppSettings[VALKEY_CACHE_APP_KEY] != null)
  495.                 endpoint = (string)ConfigurationManager.AppSettings[VALKEY_CACHE_APP_KEY];
  496.             options = new ConfigurationOptions
  497.             {
  498.                 EndPoints = { endpoint },
  499.                 Ssl = true
  500.             };
  501.             if (connMux == null)
  502.                 connMux = ConnectionMultiplexer.Connect(options);
  503.             if (db == null)
  504.                 db = connMux.GetDatabase();
  505.         }
  506.  
  507.  
  508.         /// <summary>
  509.         /// GetString gets a string value by RedisCache key
  510.         /// </summary>
  511.         /// <param name="redIsKey">key</param>
  512.         /// <param name="flags"><see cref="CommandFlags"/></param>
  513.         /// <returns>(<see cref="string"/>) value for key redIsKey</returns>
  514.         public string GetString(string redIsKey, CommandFlags flags = CommandFlags.None)
  515.         {
  516.             return Db.StringGet(redIsKey, flags);
  517.         }
  518.  
  519.         /// <summary>
  520.         /// SetString set key with string value
  521.         /// </summary>
  522.         /// <param name="redIsKey">key for string/param>
  523.         /// <param name="redIsString"></param>
  524.         /// <param name="expiry"></param>
  525.         /// <param name="keepTtl"></param>
  526.         /// <param name="when"></param>
  527.         /// <param name="flags"></param>
  528.         public bool SetString(string redIsKey, string redIsString, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None)
  529.         {
  530.             bool success = false;
  531.             lock (_lock)
  532.             {
  533.                 var allRedIsKeys = GetAllKeys();
  534.                 success = Db.StringSet(redIsKey, redIsString, expiry, when, flags);
  535.  
  536.                 if (success && !allRedIsKeys.Contains(redIsKey))
  537.                 {
  538.                     allRedIsKeys.Add(redIsKey);
  539.                     string jsonVal = JsonConvert.SerializeObject(AllKeys);
  540.                     success = Db.StringSet(ALL_KEYS, jsonVal, null, keepTtl, When.Always, CommandFlags.None);
  541.                     _allKeys = allRedIsKeys;
  542.                 }
  543.             }
  544.  
  545.             return success;
  546.         }
  547.  
  548.         /// <summary>
  549.         /// SetValue sets value to cache
  550.         /// </summary>
  551.         /// <typeparam name="T">typeparameter</typeparam>
  552.         /// <param name="ckey">key to set</param>
  553.         /// <param name="tvalue">generic value</param>
  554.         /// <returns>success on true</returns>
  555.         public override bool SetValue<T>(string ckey, T tvalue)
  556.         {
  557.             TimeSpan? expiry = null;
  558.             bool keepTtl = false;
  559.             When when = When.Always;
  560.             CommandFlags flags = CommandFlags.None;
  561.             string jsonVal = JsonConvert.SerializeObject(tvalue);
  562.             bool success = SetString(ckey, jsonVal, expiry, keepTtl, when, flags);
  563.  
  564.             return success;
  565.         }
  566.  
  567.         /// <summary>
  568.         /// gets a generic class type T from redis cache with key
  569.         /// </summary>
  570.         /// <typeparam name="T"></typeparam>
  571.         /// <param name="ckey">rediskey</param>
  572.         /// <returns>T value/returns>
  573.         public override T GetValue<T>(string ckey)
  574.         {
  575.             CommandFlags flags = CommandFlags.None;
  576.             string jsonVal = Db.StringGet(ckey, flags);
  577.             T tval = default(T);
  578.             if (jsonVal != null)
  579.             {
  580.                 tval = JsonConvert.DeserializeObject<T>(jsonVal);
  581.             }
  582.  
  583.             return tval;
  584.         }
  585.  
  586.         /// <summary>
  587.         /// DeleteKey delete entry referenced at key
  588.         /// </summary>
  589.         /// <param name="redIsKey">key</param>
  590.         /// <param name="flags"><see cref="CommandFlags.FireAndForget"/> as default</param>
  591.         public override bool RemoveKey(string redIsKey)
  592.         {
  593.             CommandFlags flags = CommandFlags.FireAndForget;
  594.             lock (_lock)
  595.             {
  596.                 var allRedIsKeys = GetAllKeys();
  597.                 if (allRedIsKeys.Contains(redIsKey))
  598.                 {
  599.                     allRedIsKeys.Remove(redIsKey);
  600.                     string jsonVal = JsonConvert.SerializeObject(allRedIsKeys.ToArray());
  601.                     Db.StringSet("AllKeys", jsonVal, null, false, When.Always, flags);
  602.                     _allKeys = allRedIsKeys;
  603.                 }
  604.                 try
  605.                 {
  606.                     TimeSpan span = new TimeSpan(0, 0, 1);
  607.                     Db.StringGetDelete(redIsKey, flags);
  608.                 }
  609.                 catch (Exception ex)
  610.                 {
  611.                     Console.Error.WriteLine($"Exception {ex.GetType()}: {ex.Message}\r\n\t{ex}");
  612.                     return false;
  613.                 }
  614.             }
  615.  
  616.             return true;
  617.         }
  618.  
  619.         /// <summary>
  620.         /// ContainsKey check if <see cref="Constants.ALL_KEYS">AllKeys</see> key contains element redIsKey
  621.         /// </summary>
  622.         /// <param name="ckey">redIsKey to search</param>
  623.         /// <returns>true, if cache contains key, otherwise false</returns>
  624.         public override bool ContainsKey(string ckey)
  625.         {
  626.             if (GetAllKeys().Contains(ckey))
  627.             {
  628.                 string redIsString = Db.StringGet(ckey, CommandFlags.None);
  629.                 if (!string.IsNullOrEmpty(redIsString))
  630.                     return true;
  631.             }
  632.  
  633.             return false;
  634.         }
  635.  
  636.         /// <summary>
  637.         /// GetAllKeys returns <see cref="HashSet{string}"/></string> <see cref="_allKeys"/>
  638.         /// </summary>
  639.         /// <returns>returns <see cref="HashSet{string}"/></string> <see cref="_allKeys"/></returns>
  640.         public static HashSet<string> GetAllKeys()
  641.         {
  642.             if (_allKeys == null || _allKeys.Count == 0)
  643.             {
  644.                 string jsonVal = Db.StringGet(ALL_KEYS, CommandFlags.None);
  645.                 string[] keys = (jsonVal != null) ? JsonConvert.DeserializeObject<string[]>(jsonVal) : new string[0];
  646.                 if (keys != null && keys.Length > 0)
  647.                     _allKeys = new HashSet<string>(keys);
  648.             }
  649.  
  650.             return _allKeys;
  651.         }
  652.     }
  653.  
  654.  
  655.     [Serializable]
  656.     public class CacheData
  657.     {
  658.         static readonly byte[] buffer = new byte[4096];
  659.         static Random random = new Random((DateTime.Now.Millisecond + 1) * (DateTime.Now.Second + 1));
  660.         [JsonIgnore]
  661.         protected internal int CIndex { get; set; }
  662.         public string CKey { get; set; }
  663.         public string CValue { get; set; }
  664.         public int CThreadId { get; set; }
  665.         public DateTime CTime { get; set; }
  666.  
  667.         static CacheData()
  668.         {
  669.             random.NextBytes(buffer);
  670.         }
  671.  
  672.         public CacheData()
  673.         {
  674.             CIndex = 0;
  675.             CValue = string.Empty;
  676.             CKey = string.Empty;
  677.             CTime = DateTime.MinValue;
  678.             CThreadId = -1;
  679.         }
  680.  
  681.         public CacheData(string ckey) : this()
  682.         {
  683.             CKey = ckey;
  684.             CIndex = Int32.Parse(ckey.Replace("Key_", ""));
  685.             CValue = GetRandomString(CIndex);
  686.             CTime = DateTime.Now;
  687.         }
  688.  
  689.         public CacheData(string ckey, int cThreadId) : this(ckey)
  690.         {
  691.             CThreadId = cThreadId;
  692.         }
  693.  
  694.         public CacheData(int cThreadId) : this(string.Concat("Key_", cThreadId))
  695.         {
  696.             CThreadId = cThreadId;
  697.         }
  698.  
  699.  
  700.         internal string GetRandomString(int ix)
  701.         {
  702.             byte[] restBytes = new byte[64];
  703.             Array.Copy(buffer, ix, restBytes, 0, 64);
  704.             return Convert.ToBase64String(restBytes, 0, 64);
  705.         }
  706.  
  707.     }
  708.  
  709.     internal class Program
  710.     {
  711.         static void Main(string[] args)
  712.         {
  713.             RunTasks(256);
  714.             RunSerial(256);
  715.  
  716.             Console.WriteLine($"\nPress any key to continue...\n");
  717.             Console.ReadKey();
  718.         }
  719.  
  720.         static void RunTasks(int numberOfTasks, short maxKexs = 16)
  721.         {
  722.             string parallelCache = MemCache.CacheVariant;
  723.             Console.WriteLine($"RunTasks(int numberOfTasks = {numberOfTasks}) cache = {parallelCache}.");
  724.             DateTime now = DateTime.Now;
  725.             if (numberOfTasks <= 0)
  726.                 numberOfTasks = 16;
  727.             if ((numberOfTasks % 4) != 0)
  728.                 numberOfTasks += (4 - (numberOfTasks % 4));
  729.  
  730.             int quater = numberOfTasks / 4;
  731.             int half = numberOfTasks / 2;
  732.             int threequater = quater + half;
  733.  
  734.             Task[] taskArray = new Task[numberOfTasks];
  735.             for (int i = 0; i < numberOfTasks; i++)
  736.             {
  737.                 if (i < quater || (i >= half && i < threequater))
  738.                 {
  739.                     taskArray[i] = Task.Factory.StartNew((object obj) =>
  740.                     {
  741.                         string ckey = string.Concat("Key_", (i % maxKexs).ToString());
  742.                         CacheData data = obj as CacheData;
  743.                         if (data == null)
  744.                             data = new CacheData(ckey, Thread.CurrentThread.ManagedThreadId);
  745.  
  746.                         data.CThreadId = Thread.CurrentThread.ManagedThreadId;
  747.                         MemCache.CacheDict.SetValue<CacheData>(ckey, data);
  748.                         // Console.WriteLine($"Task set cache key #{data.CKey} created at {data.CTime} on thread #{data.CThreadId}.");
  749.                     },
  750.                     new CacheData("Key_" + (i % maxKexs).ToString()));
  751.                 }
  752.                 else if ((i >= quater && i < half) || i >= threequater)
  753.                 {
  754.                     taskArray[i] = Task.Factory.StartNew((object obj) =>
  755.                     {
  756.                         string ckey = string.Concat("Key_", (i % maxKexs).ToString());
  757.                         string strkey = obj as string;
  758.                         if (string.IsNullOrEmpty(strkey))
  759.                             strkey = ckey;
  760.  
  761.                         CacheData data = (CacheData)MemCache.CacheDict[strkey];
  762.                         // Console.WriteLine($"Task get cache key #{strkey} => {data.CValue} created at {data.CTime} original thread {data.CThreadId} on current thread #{Thread.CurrentThread.ManagedThreadId}.");
  763.                     },
  764.                     new StringBuilder(string.Concat("Key_", (i % maxKexs).ToString())).ToString());
  765.                 }
  766.             }
  767.  
  768.             Task.WaitAll(taskArray);
  769.  
  770.             TimeSpan ts = DateTime.Now.Subtract(now);
  771.             double doublePerSecond = numberOfTasks / ts.TotalSeconds;
  772.             if (numberOfTasks > ts.TotalSeconds)
  773.                 doublePerSecond = (1000000 * numberOfTasks) / ts.TotalMicroseconds;
  774.             ulong perSecond = (ulong)doublePerSecond;
  775.             Console.WriteLine($"Finished {numberOfTasks} parallel tasks in {ts.Minutes:d2}:{ts.Seconds:d2}.{ts.Milliseconds:d3}.{ts.Microseconds:d3}\n\t{perSecond} tasks per second.\n");
  776.         }
  777.  
  778.         static void RunSerial(int iterationsCount, short maxKexs = 16)
  779.         {
  780.             string serialSache = MemCache.CacheVariant;
  781.             Console.WriteLine($"RunSerial(int iterationsCount = {iterationsCount}) cache = {serialSache}.");
  782.  
  783.             if (iterationsCount <= 0)
  784.                 iterationsCount = 16;
  785.             if ((iterationsCount % 4) != 0)
  786.                 iterationsCount += (4 - (iterationsCount % 4));
  787.             int quater = iterationsCount / 4;
  788.             int half = iterationsCount / 2;
  789.             int threequater = quater + half;
  790.  
  791.             DateTime now = DateTime.Now;
  792.             for (int i = 0; i < iterationsCount; i++)
  793.             {
  794.                 if (i < quater || (i >= half && i < threequater))
  795.                 {
  796.                     string ckey = string.Concat("Key_", (i % maxKexs).ToString());
  797.                     CacheData data = new CacheData(ckey, Thread.CurrentThread.ManagedThreadId);                    
  798.                     MemCache.CacheDict.SetValue<CacheData>(ckey, data);
  799.                     // Console.WriteLine($"Task set cache key #{data.CKey} created at {data.CTime} on thread #{data.CThreadId}.");
  800.                 }
  801.                 else if ((i >= quater && i < half) || i >= threequater)
  802.                 {
  803.                     string strkey = "Key_" + (i % maxKexs).ToString();
  804.                     CacheData cacheData = (CacheData)MemCache.CacheDict[strkey];
  805.                     // Console.WriteLine($"Task get cache key #{strkey} => {cacheData.CValue} created at {cacheData.CTime} original thread {cacheData.CThreadId} on current thread #{Thread.CurrentThread.ManagedThreadId}.");
  806.                 }
  807.             }
  808.  
  809.             // var tasks = new List<Task>(taskArray);            
  810.             // Parallel.ForEach(tasks, task => { task.Start(); });
  811.             //Task.WhenAll(tasks).ContinueWith(done => { Console.WriteLine("done"); });
  812.  
  813.             TimeSpan ts = DateTime.Now.Subtract(now);
  814.             double doublePerSecond = iterationsCount / ts.TotalSeconds;
  815.             if (iterationsCount > ts.TotalSeconds)
  816.                 doublePerSecond = (1000000 * iterationsCount) / ts.TotalMicroseconds;
  817.             ulong perSecond = (ulong)doublePerSecond;
  818.             Console.WriteLine($"Finished {iterationsCount} iterations in {ts.Minutes:d2}:{ts.Seconds:d2}.{ts.Milliseconds:d3}.{ts.Microseconds:d3}\n\t{perSecond} iterations per second.\n");
  819.  
  820.         }
  821.  
  822.     }
  823.  
  824. }
  825.  
  826.  
Add Comment
Please, Sign In to add comment