Commit 4355e858 authored by 阿星Plus's avatar 阿星Plus

MemoryCache

parent 1abcfbfe
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Castle.Core" Version="4.4.0" /> <PackageReference Include="Castle.Core" Version="4.4.0" />
<PackageReference Include="Castle.Windsor" Version="5.0.0" /> <PackageReference Include="Castle.Windsor" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="2.2.0" />
<PackageReference Include="Nito.AsyncEx.Coordination" Version="5.0.0" />
<PackageReference Include="System.Collections.Immutable" Version="1.5.0" /> <PackageReference Include="System.Collections.Immutable" Version="1.5.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.5.0" /> <PackageReference Include="System.ComponentModel.Annotations" Version="4.5.0" />
</ItemGroup> </ItemGroup>
......
using Castle.Core.Logging;
using Nito.AsyncEx;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Plus.Runtime.Caching
{
/// <summary>
/// Base class for caches.
/// It's used to simplify implementing <see cref="ICache"/>.
/// </summary>
public abstract class CacheBase : ICache
{
public ILogger Logger { get; set; }
public string Name { get; }
public TimeSpan DefaultSlidingExpireTime { get; set; }
public TimeSpan? DefaultAbsoluteExpireTime { get; set; }
protected readonly object SyncObj = new object();
private readonly AsyncLock _asyncLock = new AsyncLock();
/// <summary>
/// Constructor.
/// </summary>
/// <param name="name"></param>
protected CacheBase(string name)
{
Name = name;
DefaultSlidingExpireTime = TimeSpan.FromHours(1);
Logger = NullLogger.Instance;
}
public virtual object Get(string key, Func<string, object> factory)
{
object item = null;
try
{
item = GetOrDefault(key);
}
catch (Exception ex)
{
Logger.Error(ex.ToString(), ex);
}
if (item == null)
{
lock (SyncObj)
{
try
{
item = GetOrDefault(key);
}
catch (Exception ex)
{
Logger.Error(ex.ToString(), ex);
}
if (item == null)
{
item = factory(key);
if (item == null)
{
return null;
}
try
{
Set(key, item);
}
catch (Exception ex)
{
Logger.Error(ex.ToString(), ex);
}
}
}
}
return item;
}
public virtual object[] Get(string[] keys, Func<string, object> factory)
{
object[] items = null;
try
{
items = GetOrDefault(keys);
}
catch (Exception ex)
{
Logger.Error(ex.ToString(), ex);
}
if (items == null)
{
items = new object[keys.Length];
}
if (items.Any(i => i == null))
{
lock (SyncObj)
{
try
{
items = GetOrDefault(keys);
}
catch (Exception ex)
{
Logger.Error(ex.ToString(), ex);
}
var fetched = new List<KeyValuePair<string, object>>();
for (var i = 0; i < items.Length; i++)
{
string key = keys[i];
object value = items[i];
if (value == null)
{
value = factory(key);
}
if (value != null)
{
fetched.Add(new KeyValuePair<string, object>(key, value));
}
}
if (fetched.Any())
{
try
{
Set(fetched.ToArray());
}
catch (Exception ex)
{
Logger.Error(ex.ToString(), ex);
}
}
}
}
return items;
}
public virtual async Task<object> GetAsync(string key, Func<string, Task<object>> factory)
{
object item = null;
try
{
item = await GetOrDefaultAsync(key);
}
catch (Exception ex)
{
Logger.Error(ex.ToString(), ex);
}
if (item == null)
{
using (await _asyncLock.LockAsync())
{
try
{
item = await GetOrDefaultAsync(key);
}
catch (Exception ex)
{
Logger.Error(ex.ToString(), ex);
}
if (item == null)
{
item = await factory(key);
if (item == null)
{
return null;
}
try
{
await SetAsync(key, item);
}
catch (Exception ex)
{
Logger.Error(ex.ToString(), ex);
}
}
}
}
return item;
}
public virtual async Task<object[]> GetAsync(string[] keys, Func<string, Task<object>> factory)
{
object[] items = null;
try
{
items = await GetOrDefaultAsync(keys);
}
catch (Exception ex)
{
Logger.Error(ex.ToString(), ex);
}
if (items == null)
{
items = new object[keys.Length];
}
if (items.Any(i => i == null))
{
using (await _asyncLock.LockAsync())
{
try
{
items = await GetOrDefaultAsync(keys);
}
catch (Exception ex)
{
Logger.Error(ex.ToString(), ex);
}
var fetched = new List<KeyValuePair<string, object>>();
for (var i = 0; i < items.Length; i++)
{
string key = keys[i];
object value = items[i];
if (value == null)
{
value = factory(key);
}
if (value != null)
{
fetched.Add(new KeyValuePair<string, object>(key, value));
}
}
if (fetched.Any())
{
try
{
await SetAsync(fetched.ToArray());
}
catch (Exception ex)
{
Logger.Error(ex.ToString(), ex);
}
}
}
}
return items;
}
public abstract object GetOrDefault(string key);
public virtual object[] GetOrDefault(string[] keys)
{
return keys.Select(GetOrDefault).ToArray();
}
public virtual Task<object> GetOrDefaultAsync(string key)
{
return Task.FromResult(GetOrDefault(key));
}
public virtual Task<object[]> GetOrDefaultAsync(string[] keys)
{
return Task.FromResult(GetOrDefault(keys));
}
public abstract void Set(string key, object value, TimeSpan? slidingExpireTime = null, TimeSpan? absoluteExpireTime = null);
public virtual void Set(KeyValuePair<string, object>[] pairs, TimeSpan? slidingExpireTime = null, TimeSpan? absoluteExpireTime = null)
{
foreach (var pair in pairs)
{
Set(pair.Key, pair.Value, slidingExpireTime, absoluteExpireTime);
}
}
public virtual Task SetAsync(string key, object value, TimeSpan? slidingExpireTime = null, TimeSpan? absoluteExpireTime = null)
{
Set(key, value, slidingExpireTime, absoluteExpireTime);
return Task.FromResult(0);
}
public virtual Task SetAsync(KeyValuePair<string, object>[] pairs, TimeSpan? slidingExpireTime = null, TimeSpan? absoluteExpireTime = null)
{
return Task.WhenAll(pairs.Select(p => SetAsync(p.Key, p.Value, slidingExpireTime, absoluteExpireTime)));
}
public abstract void Remove(string key);
public virtual void Remove(string[] keys)
{
foreach (var key in keys)
{
Remove(key);
}
}
public virtual Task RemoveAsync(string key)
{
Remove(key);
return Task.FromResult(0);
}
public virtual Task RemoveAsync(string[] keys)
{
return Task.WhenAll(keys.Select(RemoveAsync));
}
public abstract void Clear();
public virtual Task ClearAsync()
{
Clear();
return Task.FromResult(0);
}
public virtual void Dispose()
{
}
}
}
\ No newline at end of file
...@@ -60,7 +60,7 @@ namespace Plus.Runtime.Caching ...@@ -60,7 +60,7 @@ namespace Plus.Runtime.Caching
var value = await cache.GetAsync(key.ToString(), async (keyAsString) => var value = await cache.GetAsync(key.ToString(), async (keyAsString) =>
{ {
var v = await factory(key); var v = await factory(key);
return (object)v; return v;
}); });
return (TValue)value; return (TValue)value;
...@@ -73,7 +73,7 @@ namespace Plus.Runtime.Caching ...@@ -73,7 +73,7 @@ namespace Plus.Runtime.Caching
return cache.GetAsync(key.ToString(), async (keyAsString) => return cache.GetAsync(key.ToString(), async (keyAsString) =>
{ {
var v = await factory(key); var v = await factory(key);
return (object)v; return v;
}); });
}); });
var values = await Task.WhenAll(tasks); var values = await Task.WhenAll(tasks);
...@@ -96,7 +96,7 @@ namespace Plus.Runtime.Caching ...@@ -96,7 +96,7 @@ namespace Plus.Runtime.Caching
var value = cache.GetOrDefault(key.ToString()); var value = cache.GetOrDefault(key.ToString());
if (value == null) if (value == null)
{ {
return default(TValue); return default;
} }
return (TValue)value; return (TValue)value;
...@@ -109,7 +109,7 @@ namespace Plus.Runtime.Caching ...@@ -109,7 +109,7 @@ namespace Plus.Runtime.Caching
var value = cache.GetOrDefault(key.ToString()); var value = cache.GetOrDefault(key.ToString());
if (value == null) if (value == null)
{ {
return default(TValue); return default;
} }
return (TValue)value; return (TValue)value;
}); });
...@@ -122,7 +122,7 @@ namespace Plus.Runtime.Caching ...@@ -122,7 +122,7 @@ namespace Plus.Runtime.Caching
var value = await cache.GetOrDefaultAsync(key.ToString()); var value = await cache.GetOrDefaultAsync(key.ToString());
if (value == null) if (value == null)
{ {
return default(TValue); return default;
} }
return (TValue)value; return (TValue)value;
...@@ -135,7 +135,7 @@ namespace Plus.Runtime.Caching ...@@ -135,7 +135,7 @@ namespace Plus.Runtime.Caching
var value = await cache.GetOrDefaultAsync(key.ToString()); var value = await cache.GetOrDefaultAsync(key.ToString());
if (value == null) if (value == null)
{ {
return default(TValue); return default;
} }
return (TValue)value; return (TValue)value;
}); });
......
using Plus.Dependency;
using Plus.Runtime.Caching.Configuration;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
namespace Plus.Runtime.Caching
{
/// <summary>
/// Base class for cache managers.
/// </summary>
public abstract class CacheManagerBase : ICacheManager, ISingletonDependency
{
protected readonly IIocManager IocManager;
protected readonly ICachingConfiguration Configuration;
protected readonly ConcurrentDictionary<string, ICache> Caches;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="iocManager"></param>
/// <param name="configuration"></param>
protected CacheManagerBase(IIocManager iocManager, ICachingConfiguration configuration)
{
IocManager = iocManager;
Configuration = configuration;
Caches = new ConcurrentDictionary<string, ICache>();
}
public IReadOnlyList<ICache> GetAllCaches()
{
return Caches.Values.ToImmutableList();
}
public virtual ICache GetCache(string name)
{
return Caches.GetOrAdd(name, (cacheName) =>
{
var cache = CreateCacheImplementation(cacheName);
var configurators = Configuration.Configurators.Where(c => c.CacheName == null || c.CacheName == cacheName);
foreach (var configurator in configurators)
{
configurator.InitAction?.Invoke(cache);
}
return cache;
});
}
public virtual void Dispose()
{
DisposeCaches();
Caches.Clear();
}
protected virtual void DisposeCaches()
{
foreach (var cache in Caches)
{
IocManager.Release(cache.Value);
}
}
/// <summary>
/// Used to create actual cache implementation.
/// </summary>
/// <param name="name">Name of the cache</param>
/// <returns>Cache object</returns>
protected abstract ICache CreateCacheImplementation(string name);
}
}
\ No newline at end of file
using Castle.Core.Logging;
using Plus.Dependency;
using Plus.Runtime.Caching.Configuration;
namespace Plus.Runtime.Caching.Memory
{
/// <summary>
/// Implements <see cref="ICacheManager"/> to work with MemoryCache.
/// </summary>
public class AbpMemoryCacheManager : CacheManagerBase
{
public ILogger Logger { get; set; }
/// <summary>
/// Constructor.
/// </summary>
public AbpMemoryCacheManager(IIocManager iocManager, ICachingConfiguration configuration)
: base(iocManager, configuration)
{
Logger = NullLogger.Instance;
}
protected override ICache CreateCacheImplementation(string name)
{
return new AbpMemoryCache(name)
{
Logger = Logger
};
}
protected override void DisposeCaches()
{
foreach (var cache in Caches.Values)
{
cache.Dispose();
}
}
}
}
\ No newline at end of file
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using System;
namespace Plus.Runtime.Caching.Memory
{
/// <summary>
/// Implements <see cref="ICache"/> to work with <see cref="MemoryCache"/>.
/// </summary>
public class AbpMemoryCache : CacheBase
{
private MemoryCache _memoryCache;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="name">Unique name of the cache</param>
public AbpMemoryCache(string name)
: base(name)
{
_memoryCache = new MemoryCache(new OptionsWrapper<MemoryCacheOptions>(new MemoryCacheOptions()));
}
public override object GetOrDefault(string key)
{
return _memoryCache.Get(key);
}
public override void Set(string key, object value, TimeSpan? slidingExpireTime = null, TimeSpan? absoluteExpireTime = null)
{
if (value == null)
{
throw new PlusException("Can not insert null values to the cache!");
}
if (absoluteExpireTime != null)
{
_memoryCache.Set(key, value, DateTimeOffset.Now.Add(absoluteExpireTime.Value));
}
else if (slidingExpireTime != null)
{
_memoryCache.Set(key, value, slidingExpireTime.Value);
}
else if (DefaultAbsoluteExpireTime != null)
{
_memoryCache.Set(key, value, DateTimeOffset.Now.Add(DefaultAbsoluteExpireTime.Value));
}
else
{
_memoryCache.Set(key, value, DefaultSlidingExpireTime);
}
}
public override void Remove(string key)
{
_memoryCache.Remove(key);
}
public override void Clear()
{
_memoryCache.Dispose();
_memoryCache = new MemoryCache(new OptionsWrapper<MemoryCacheOptions>(new MemoryCacheOptions()));
}
public override void Dispose()
{
_memoryCache.Dispose();
base.Dispose();
}
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment