diff --git a/AsbCloudInfrastructure/Services/Cache/EfCacheL2.cs b/AsbCloudInfrastructure/Services/Cache/EfCacheL2.cs
index ad503b29..cd147d92 100644
--- a/AsbCloudInfrastructure/Services/Cache/EfCacheL2.cs
+++ b/AsbCloudInfrastructure/Services/Cache/EfCacheL2.cs
@@ -1,6 +1,6 @@
-using System;
+using Microsoft.EntityFrameworkCore;
+using System;
using System.Collections;
-using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@@ -9,44 +9,67 @@ using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Services.Cache
{
#nullable enable
+ ///
+ /// Кеширование запросов EF.
+ ///
public static class EfCacheL2
{
- public static int RequestsToDb = 0;
-
- static readonly ConcurrentDictionary> caches = new(1, 16);
-
- private const int semaphoreTimeout = 25_000;
+ ///
+ /// Кол-во обращений к БД.
+ ///
+ public static int CountOfRequestsToDB = 0;
+ private static readonly Dictionary caches = new(16);
+ private static readonly TimeSpan semaphoreTimeout = TimeSpan.FromSeconds(25);
private static readonly SemaphoreSlim semaphore = new(1);
+ private static readonly TimeSpan minCacheTime = TimeSpan.FromSeconds(2);
- private struct CacheItem
+ private class CacheItem
{
- public readonly IEnumerable Data;
- public readonly DateTime DateObsolete;
-
- public CacheItem(IEnumerable data, TimeSpan obsolescence)
- {
- DateObsolete = DateTime.Now + obsolescence;
- Data = data;
- }
+ internal IEnumerable? Data;
+ internal DateTime DateObsolete;
+ internal DateTime DateObsoleteTotal;
+ internal readonly SemaphoreSlim semaphore = new(1);
}
- private static CacheItem GetOrAddCache(string tag, Func valueFactory)
+ private static CacheItem GetOrAddCache(string tag, Func valueFactory, TimeSpan obsolete)
{
- Lazy? lazyCache;
- while (!caches.TryGetValue(tag, out lazyCache))
+ CacheItem cache;
+ while (!caches.ContainsKey(tag))
{
if (semaphore.Wait(0))
{
- lazyCache = new Lazy(valueFactory);
- caches.TryAdd(tag, lazyCache);
- _ = lazyCache.Value;
- semaphore.Release();
+ try {
+ cache = new CacheItem();
+
+ var dateObsolete = DateTime.Now + obsolete;
+ var dateQueryStart = DateTime.Now;
+ var data = valueFactory();
+ var queryTime = DateTime.Now - dateQueryStart;
+
+ if (dateObsolete - DateTime.Now < minCacheTime)
+ dateObsolete = DateTime.Now + minCacheTime;
+
+ cache.Data = data;
+ cache.DateObsolete = dateObsolete;
+ cache.DateObsoleteTotal = dateObsolete + queryTime + minCacheTime;
+ caches.Add(tag, cache);
+ }
+ catch
+ {
+ throw;
+ }
+ finally
+ {
+ semaphore.Release();
+ }
break;
}
else
{
- if(semaphore.Wait(semaphoreTimeout))
+ if (semaphore.Wait(semaphoreTimeout))
+ {
semaphore.Release();
+ }
else
{
semaphore.Release();
@@ -55,55 +78,91 @@ namespace AsbCloudInfrastructure.Services.Cache
}
}
- if (lazyCache.Value.DateObsolete < DateTime.Now)
+ cache = caches[tag];
+
+ if (cache.DateObsolete < DateTime.Now)
{
- var isUpdated = false;
- if (semaphore.Wait(0))
+ if (cache.semaphore.Wait(0))
{
- lazyCache = new Lazy(valueFactory);
- caches.Remove(tag, out _);
- caches.TryAdd(tag, lazyCache);
- _ = lazyCache.Value;
- isUpdated = true;
- semaphore.Release();
+ try
+ {
+ var dateObsolete = DateTime.Now + obsolete;
+ var dateQueryStart = DateTime.Now;
+ var data = valueFactory();
+ var queryTime = DateTime.Now - dateQueryStart;
+
+ if (dateObsolete - DateTime.Now < minCacheTime)
+ dateObsolete = DateTime.Now + minCacheTime;
+
+ cache.Data = data;
+ cache.DateObsolete = dateObsolete;
+ cache.DateObsoleteTotal = dateObsolete + queryTime + minCacheTime;
+ }
+ catch
+ {
+ throw;
+ }
+ finally
+ {
+ cache.semaphore.Release();
+ }
}
- else
+ else if(cache.DateObsoleteTotal < DateTime.Now)
{
- if (semaphore.Wait(semaphoreTimeout))
- semaphore.Release();
+ if (cache.semaphore.Wait(semaphoreTimeout))
+ {
+ cache.semaphore.Release();
+ }
else
{
- semaphore.Release();
+ cache.semaphore.Release();
throw new TimeoutException("EfCacheL2.GetOrAddCache. Can't wait too long while getting cache");
}
- }
-
- if (isUpdated || caches.TryGetValue(tag, out lazyCache))
- return lazyCache.Value;
-
- throw new Exception("EfCacheL2.GetOrAddCache it should never happens");
+ }
}
- else
- return lazyCache.Value;
+ return cache;
}
- private static async Task GetOrAddCacheAsync(string tag, Func valueFactory, CancellationToken token)
+ private static async Task GetOrAddCacheAsync(string tag, Func> valueFactoryAsync, TimeSpan obsolete, CancellationToken token)
{
- Lazy? lazyCache;
- while (!caches.TryGetValue(tag, out lazyCache))
+ CacheItem cache;
+ while (!caches.ContainsKey(tag))
{
- if (semaphore.Wait(0, CancellationToken.None))
+ if (semaphore.Wait(0))
{
- lazyCache = new Lazy(valueFactory);
- caches.TryAdd(tag, lazyCache);
- _ = lazyCache.Value;
- semaphore.Release();
+ try
+ {
+ cache = new CacheItem();
+
+ var dateObsolete = DateTime.Now + obsolete;
+ var dateQueryStart = DateTime.Now;
+ var data = await valueFactoryAsync(token);
+ var queryTime = DateTime.Now - dateQueryStart;
+
+ if (dateObsolete - DateTime.Now < minCacheTime)
+ dateObsolete = DateTime.Now + minCacheTime;
+
+ cache.Data = data;
+ cache.DateObsolete = dateObsolete;
+ cache.DateObsoleteTotal = dateObsolete + queryTime + minCacheTime;
+ caches.Add(tag, cache);
+ }
+ catch
+ {
+ throw;
+ }
+ finally
+ {
+ semaphore.Release();
+ }
break;
}
else
{
if (await semaphore.WaitAsync(semaphoreTimeout, token))
+ {
semaphore.Release();
+ }
else
{
semaphore.Release();
@@ -112,39 +171,52 @@ namespace AsbCloudInfrastructure.Services.Cache
}
}
- if (lazyCache.Value.DateObsolete < DateTime.Now)
+ cache = caches[tag];
+
+ if (cache.DateObsolete < DateTime.Now)
{
- var isUpdated = false;
- if (semaphore.Wait(0, CancellationToken.None))
+ if (cache.semaphore.Wait(0))
{
- lazyCache = new Lazy(valueFactory);
- caches.Remove(tag, out _);
- caches.TryAdd(tag, lazyCache);
- _ = lazyCache.Value;
- isUpdated = true;
- semaphore.Release();
- }
- else
- {
- if (await semaphore.WaitAsync(semaphoreTimeout, token))
- semaphore.Release();
- else
+ try
{
- semaphore.Release();
- throw new TimeoutException("EfCacheL2.GetOrAddCache. Can't wait too long while getting cache");
+ var dateObsolete = DateTime.Now + obsolete;
+ var dateQueryStart = DateTime.Now;
+ var data = await valueFactoryAsync(token);
+ var queryTime = DateTime.Now - dateQueryStart;
+
+ if (dateObsolete - DateTime.Now < minCacheTime)
+ dateObsolete = DateTime.Now + minCacheTime;
+
+ cache.Data = data;
+ cache.DateObsolete = dateObsolete;
+ cache.DateObsoleteTotal = dateObsolete + queryTime + minCacheTime;
+ }
+ catch
+ {
+ throw;
+ }
+ finally
+ {
+ cache.semaphore.Release();
+ }
+ }
+ else if (cache.DateObsoleteTotal < DateTime.Now)
+ {
+ if (await cache.semaphore.WaitAsync(semaphoreTimeout, token))
+ {
+ cache.semaphore.Release();
+ }
+ else
+ {
+ cache.semaphore.Release();
+ throw new TimeoutException("EfCacheL2.GetOrAddCache. Can't wait too long while getting updated cache");
}
}
-
- if (isUpdated || caches.TryGetValue(tag, out lazyCache))
- return lazyCache.Value;
-
- throw new Exception("EfCacheL2.GetOrAddCache it should never happens");
}
- else
- return lazyCache.Value;
+ return cache;
}
- private static IEnumerable ConvertToIEnumerable(IEnumerable data)
+ private static IEnumerable ConvertToIEnumerable(IEnumerable? data)
{
if (data is IEnumerable list)
return list;
@@ -157,84 +229,177 @@ namespace AsbCloudInfrastructure.Services.Cache
throw new NotSupportedException("cache.Data has wrong type.");
}
- private static Dictionary ConvertToDictionary(IEnumerable data, Func keySelector)
+ private static Dictionary ConvertToDictionary(IEnumerable? data, Func keySelector)
where TKey : notnull
{
if (data is Dictionary dictionary)
return dictionary;
- else
+ else if (data is IEnumerable enumerable)
{
System.Diagnostics.Trace.TraceWarning($"ConvertToDictionary. Use keyed method on keyless cache. Type: {typeof(T).Name};");
- return ((IEnumerable)data).ToDictionary(keySelector);
+ return enumerable.ToDictionary(keySelector);
}
+ else
+ throw new NotSupportedException("cache.Data has wrong type.");
}
- private static Func MakeValueListFactory(IQueryable query, TimeSpan obsolescence)
- {
- CacheItem ValueFactory()
- {
- var list = query.ToList();
- RequestsToDb++;
- return new CacheItem(list, obsolescence);
- }
- return ValueFactory;
- }
-
- private static Func MakeValueDictionaryFactory(IQueryable query, TimeSpan obsolescence, Func keySelector)
- where TKey : notnull
- {
- CacheItem ValueFactory()
- {
- var dictionary = query.ToDictionary(keySelector);
- RequestsToDb++;
- return new CacheItem(dictionary, obsolescence);
- };
- return ValueFactory;
- }
-
+ ///
+ /// Кешировать запрос в List\.
+ /// Выборки по PK будут работать медленнее, чем при кешировании в виде словаря.
+ ///
+ ///
+ ///
+ /// Метка кеша
+ /// Период устаревания данных
+ ///
public static IEnumerable FromCache(this IQueryable query, string tag, TimeSpan obsolescence)
{
- var factory = MakeValueListFactory(query, obsolescence);
- var cache = GetOrAddCache(tag, factory);
+ IEnumerable factory ()
+ {
+ CountOfRequestsToDB++;
+ return query.ToList();
+ }
+ var cache = GetOrAddCache(tag, factory, obsolescence);
return ConvertToIEnumerable(cache.Data);
}
+ ///
+ /// Асинхронно кешировать запрос в List\.
+ /// Выборки по PK будут работать медленнее, чем при кешировании в виде словаря.
+ ///
+ ///
+ ///
+ /// Метка кеша
+ /// Период устаревания данных
+ ///
+ ///
public static async Task> FromCacheAsync(this IQueryable query, string tag, TimeSpan obsolescence, CancellationToken token = default)
{
- var factory = MakeValueListFactory(query, obsolescence);
- var cache = await GetOrAddCacheAsync(tag, factory, token);
+ async Task factory(CancellationToken token)
+ {
+ CountOfRequestsToDB++;
+ return await query.ToListAsync(token);
+ }
+ var cache = await GetOrAddCacheAsync(tag, factory, obsolescence, token);
return ConvertToIEnumerable(cache.Data);
}
+ ///
+ /// Кешировать запрос в Dictionary\.
+ ///
+ /// тип ключа
+ /// тип значения
+ ///
+ /// Метка кеша
+ /// Период устаревания данных
+ /// Делегат получения ключа из записи
+ ///
+ ///
+ ///
+ ///
public static Dictionary FromCache(this IQueryable query, string tag, TimeSpan obsolescence, Func keySelector)
where TKey: notnull
{
- var factory = MakeValueDictionaryFactory(query, obsolescence, keySelector);
- var cache = GetOrAddCache(tag, factory);
+ IEnumerable factory ()
+ {
+ CountOfRequestsToDB++;
+ return query.ToDictionary(keySelector);
+ }
+ var cache = GetOrAddCache(tag, factory, obsolescence);
return ConvertToDictionary(cache.Data, keySelector);
}
+ ///
+ /// Асинхронно кешировать запрос в Dictionary\.
+ ///
+ /// тип ключа
+ /// тип значения
+ ///
+ /// Метка кеша
+ /// Период устаревания данных
+ /// Делегат получения ключа из записи
+ ///
+ ///
public static async Task> FromCacheAsync(this IQueryable query, string tag, TimeSpan obsolescence, Func keySelector, CancellationToken token = default)
where TKey : notnull
{
- var factory = MakeValueDictionaryFactory(query, obsolescence, keySelector);
- var cache = await GetOrAddCacheAsync(tag, factory, token);
+ async Task factory(CancellationToken token)
+ {
+ CountOfRequestsToDB++;
+ return await query.ToDictionaryAsync(keySelector, token);
+ }
+ var cache = await GetOrAddCacheAsync(tag, factory, obsolescence, token);
return ConvertToDictionary(cache.Data, keySelector);
}
+ ///
+ /// Получить запись из кеша по ключу.
+ /// При отсутствии кеша создаст его для всех записей из query.
+ ///
+ /// тип ключа
+ /// тип значения
+ ///
+ /// Метка кеша
+ /// Период устаревания данных
+ /// Делегат получения ключа из записи
+ ///
+ ///
+ /// if cache contains trash
public static T? FromCacheGetValueOrDefault(this IQueryable query, string tag, TimeSpan obsolescence, Func keySelector, TKey key)
where TKey : notnull
{
- var factory = MakeValueDictionaryFactory(query, obsolescence, keySelector);
- var cache = GetOrAddCache(tag, factory);
-
- if (cache.Data is Dictionary dictionary)
+ IEnumerable factory()
+ {
+ CountOfRequestsToDB++;
+ return query.ToDictionary(keySelector);
+ }
+ var cache = GetOrAddCache(tag, factory, obsolescence);
+ var data = cache.Data;
+ if (data is Dictionary dictionary)
return dictionary.GetValueOrDefault(key);
- else
+ else if (data is IEnumerable enumerable)
{
System.Diagnostics.Trace.TraceWarning($"Use keyed method on keyless cache. Tag: {tag}, type: {typeof(T).Name};");
- return ((IEnumerable)cache.Data).FirstOrDefault(v => keySelector(v).Equals(key));
+ return enumerable.FirstOrDefault(v => keySelector(v).Equals(key));
}
+ else
+ throw new NotSupportedException("cache.Data has wrong type.");
+ }
+
+ ///
+ /// Асинхронно получить запись из кеша по ключу.
+ /// При отсутствии кеша создаст его для всех записей из query.
+ ///
+ /// тип ключа
+ /// тип значения
+ ///
+ /// Метка кеша
+ /// Период устаревания данных
+ /// Делегат получения ключа из записи
+ ///
+ ///
+ ///
+ /// if cache contains trash
+ public static async Task FromCacheGetValueOrDefaultAsync(this IQueryable query, string tag, TimeSpan obsolescence, Func keySelector, TKey key, CancellationToken token = default)
+ where TKey : notnull
+ {
+ async Task factory(CancellationToken token)
+ {
+ CountOfRequestsToDB++;
+ return await query.ToDictionaryAsync(keySelector, token);
+ }
+ var cache = await GetOrAddCacheAsync(tag, factory, obsolescence, token);
+
+ var data = cache.Data;
+ if (data is Dictionary dictionary)
+ return dictionary.GetValueOrDefault(key);
+ else if (data is IEnumerable enumerable)
+ {
+ System.Diagnostics.Trace.TraceWarning($"Use keyed method on keyless cache. Tag: {tag}, type: {typeof(T).Name};");
+ return enumerable.FirstOrDefault(v => keySelector(v).Equals(key));
+ }
+ else
+ throw new NotSupportedException("cache.Data has wrong type.");
}
public static void DropCache(this IQueryable query, string tag)
diff --git a/ConsoleApp1/ConsoleApp1.csproj b/ConsoleApp1/ConsoleApp1.csproj
index 1b11ff58..e0d86f92 100644
--- a/ConsoleApp1/ConsoleApp1.csproj
+++ b/ConsoleApp1/ConsoleApp1.csproj
@@ -14,8 +14,8 @@
-
-
+
+
diff --git a/ConsoleApp1/Program.cs b/ConsoleApp1/Program.cs
index 47aafc94..44c26ec9 100644
--- a/ConsoleApp1/Program.cs
+++ b/ConsoleApp1/Program.cs
@@ -1,7 +1,6 @@
using AsbCloudApp.Data;
using AsbCloudDb.Model;
using AsbCloudInfrastructure.Services.Cache;
-//using AsbCloudInfrastructure.Services.Cache;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
@@ -14,40 +13,18 @@ namespace ConsoleApp1
class Program
{
+ // use ServiceFactory to make services
static void Main(/*string[] args*/)
{
- TypeAdapterConfig.GlobalSettings.Default.Config
- .ForType()
- .MapWith((source) => source.DateTime);
-
- TypeAdapterConfig.GlobalSettings.Default.Config
- .ForType()
- .MapWith((source) => source == default ? new DateTime(0, DateTimeKind.Utc) : source);
-
- TypeAdapterConfig.GlobalSettings.Default.Config
- .ForType()
- .MapWith((source) => source == default? default: source.MakeTimeOnly());
-
- TypeAdapterConfig.GlobalSettings.Default.Config
- .ForType()
- .MapWith((source) => new (source));
-
- var sh = new ScheduleDto{
- ShiftStart = new TimeDto { Hour = 11, Minute = 30, }
- };
- var en = sh.Adapt();
- var aa = en.Adapt();
- // use ServiceFactory to make services
Console.WriteLine("hit keyboard to start");
Console.ReadLine();
for (int i = 0; i < 24; i++)
{
- //Thread.Sleep(3000);
var t = new Thread(_ => {
for (int j = 0; j < 64; j++)
- //Task.Run(GetClastersAsync).Wait();
- GetClasters();
+ Task.Run(GetClastersAsync).Wait();
+ //GetClasters();
});
t.Start();
}
@@ -65,14 +42,14 @@ namespace ConsoleApp1
.Where(t => t.IdTelemetry == 135)
.OrderBy(t => t.DateTime)
.Take(100_000)
- .FromCache("tds", obso, r=>(r.IdTelemetry, r.DateTime))
+ .FromCache("tds", obso)
.ToList();
sw.Stop();
- Console.WriteLine($"{DateTime.Now}\tth: {Thread.CurrentThread.ManagedThreadId}\trequests {EfCacheL2.RequestsToDb}\ttime {sw.ElapsedMilliseconds}\tcount {cs.Count}");
- //Console.WriteLine($"{DateTime.Now}\tth: {Thread.CurrentThread.ManagedThreadId}\ttime {sw.ElapsedMilliseconds}\tcount {cs.Count}");
+ //Console.WriteLine($"{DateTime.Now}\tth: {Thread.CurrentThread.ManagedThreadId}\trequests {EfCacheL2rev4.RequestsToDb}\ttime {sw.ElapsedMilliseconds}\tcount {cs.Count}");
+ Console.WriteLine($"{DateTime.Now}\tth: {Thread.CurrentThread.ManagedThreadId}\ttime {sw.ElapsedMilliseconds}\tcount {cs.Count}");
GC.Collect();
- Thread.Sleep(100);
+ Thread.Sleep(10);
return (cs.Count, sw.ElapsedMilliseconds);
}
@@ -87,11 +64,11 @@ namespace ConsoleApp1
.FromCacheAsync("tds", obso, r => (r.IdTelemetry, r.DateTime)))
.ToList();
sw.Stop();
- Console.WriteLine($"{DateTime.Now}\tth: {Thread.CurrentThread.ManagedThreadId}\trequests {EfCacheL2.RequestsToDb}\ttime {sw.ElapsedMilliseconds}\tcount {cs.Count}");
- //Console.WriteLine($"{DateTime.Now}\tth: {Thread.CurrentThread.ManagedThreadId}\ttime {sw.ElapsedMilliseconds}\tcount {cs.Count}");
+ //Console.WriteLine($"{DateTime.Now}\tth: {Thread.CurrentThread.ManagedThreadId}\trequests {EfCacheL2rev4.RequestsToDb}\ttime {sw.ElapsedMilliseconds}\tcount {cs.Count}");
+ Console.WriteLine($"{DateTime.Now}\tth: {Thread.CurrentThread.ManagedThreadId}\ttime {sw.ElapsedMilliseconds}\tcount {cs.Count}");
GC.Collect();
- Thread.Sleep(100);
+ Thread.Sleep(10);
return (cs.Count, sw.ElapsedMilliseconds);
}
}