这段代码似乎可以很好地缓存异步方法的结果。我想为它添加一些过期。我已经尝试了Tuple,但是我没有成功地完成工作/编译。
private static readonly ConcurrentDictionary<object, SemaphoreSlim> _keyLocks = new ConcurrentDictionary<object, SemaphoreSlim>();
private static readonly ConcurrentDictionary<object, Tuple<List<UnitDTO>, DateTime>> _cache = new ConcurrentDictionary<object, Tuple<List<UnitDTO>, DateTime>>();
public async Task<string> GetSomethingAsync(string key)
{
string value;
// get the semaphore specific to this key
var keyLock = _keyLocks.GetOrAdd(key, x => new SemaphoreSlim(1));
await keyLock.WaitAsync();
try
{
// try to get value from cache
if (!_cache.TryGetValue(key, out value))
{
// if value isn't cached, get it the long way asynchronously
value = await GetSomethingTheLongWayAsync();
// cache value
_cache.TryAdd(key, value);
}
}
finally
{
keyLock.Release();
}
return value;
}
答案 0 :(得分:1)
来自msdn,来自Stephen Cleary
异步代码通常用于初始化当时的资源 缓存和共享。没有内置类型,但斯蒂芬 Toub开发了一个AsyncLazy,就像Task和 懒。原始类型在他的blog上描述,并且是 我的AsyncEx library中提供了更新版本。
public class AsyncLazy<T> : Lazy<Task<T>>
{
public AsyncLazy(Func<T> valueFactory) :
base(() => Task.Factory.StartNew(valueFactory)) { }
public AsyncLazy(Func<Task<T>> taskFactory) :
base(() => Task.Factory.StartNew(() => taskFactory()).Unwrap()) { }
}
<强>上下文强>
假设我们的程序中有一个AsyncLazy实例:
static string LoadString() { … }
static AsyncLazy<string> m_data = new AsyncLazy<string>(LoadString);
<强>用法强>
因此,我们可以编写一个执行以下操作的异步方法:
string data = await m_data.Value;
Lazy<T>
是合适的,但很遗憾它似乎缺少输入参数来索引结果。同样的问题已解决here,其中解释了如何缓存长期运行的资源密集型方法的结果,以防它不 async
在我展示与缓存管理相关的主要更改并且特定于您提议的实施之前,让我建议一些边缘优化选项,基于以下{ {3}}
经常使用锁,当你访问它们时,它们是无竞争的,并且在 在这种情况下,你真的想要获取并释放锁定为 尽可能低开销;换句话说,访问无竞争锁 应该涉及一条快速路径
由于他们只是性能优化技巧,我会在代码中对其进行评论,以便您可以衡量在特定情况下的效果前
这个开销与缓存未命中的平衡已经在之前的concerns指出了类似的问题。
显然,开销保持SemaphoreSlim对象的存在 防止缓存未命中,因此根据用途可能不值得 案件。但是,如果保证没有缓存未命中比这更重要 完成了。
关于缓存过期,我建议将创建DateTime添加到Dictionary的值(即从GetSomethingTheLongWayAsync返回值的时间),并因此在固定的时间跨度后丢弃缓存的值。
查找以下草稿
private static readonly ConcurrentDictionary<object, SemaphoreSlim> _keyLocks = new ConcurrentDictionary<object, SemaphoreSlim>();
private static readonly ConcurrentDictionary<object, Tuple<string, DateTime>> _cache = new ConcurrentDictionary<object, Tuple<string, DateTime>>();
private static bool IsExpiredDelete(Tuple<string, DateTime> value, string key)
{
bool _is_exp = (DateTime.Now - value.Item2).TotalMinutes > Expiration;
if (_is_exp)
{
_cache.TryRemove(key, out value);
}
return _is_exp;
}
public async Task<string> GetSomethingAsync(string key)
{
Tuple<string, DateTime> cached;
// get the semaphore specific to this key
var keyLock = _keyLocks.GetOrAdd(key, x => new SemaphoreSlim(1));
await keyLock.WaitAsync();
try
{
// try to get value from cache
if (!_cache.TryGetValue(key, out cached) || IsExpiredDelete(cached,key))
{
//possible performance optimization: measure it before uncommenting
//keyLock.Release();
string value = await GetSomethingTheLongWayAsync(key);
DateTime creation = DateTime.Now;
// in case of performance optimization
// get the semaphore specific to this key
//keyLock = _keyLocks.GetOrAdd(key, x => new SemaphoreSlim(1));
//await keyLock.WaitAsync();
bool notFound;
if (notFound = !_cache.TryGetValue(key, out cached) || IsExpiredDelete(cached, key))
{
cached = new Tuple<string, DateTime>(value, creation);
_cache.TryAdd(key, cached);
}
else
{
if (!notFound && cached.Item2 < creation)
{
cached = new Tuple<string, DateTime>(value, creation);
_cache.TryAdd(key, cached);
}
}
}
}
finally
{
keyLock.Release();
}
return cached?.Item1;
}
请根据您的具体需求调整上述代码。
最后你可能想稍微概括一下。
顺便提一下,请注意Dictionary
不 static
,因为可以使用相同的<来缓存两个不同的方法/ em>签名。
public class Cached<FromT, ToT>
{
private Func<FromT, Task<ToT>> GetSomethingTheLongWayAsync;
public Cached (Func<FromT, Task<ToT>> _GetSomethingTheLongWayAsync, int expiration_min ) {
GetSomethingTheLongWayAsync = _GetSomethingTheLongWayAsync;
Expiration = expiration_min;
}
int Expiration = 1;
private ConcurrentDictionary<FromT, SemaphoreSlim> _keyLocks = new ConcurrentDictionary<FromT, SemaphoreSlim>();
private ConcurrentDictionary<FromT, Tuple<ToT, DateTime>> _cache = new ConcurrentDictionary<FromT, Tuple<ToT, DateTime>>();
private bool IsExpiredDelete(Tuple<ToT, DateTime> value, FromT key)
{
bool _is_exp = (DateTime.Now - value.Item2).TotalMinutes > Expiration;
if (_is_exp)
{
_cache.TryRemove(key, out value);
}
return _is_exp;
}
public async Task<ToT> GetSomethingAsync(FromT key)
{
Tuple<ToT, DateTime> cached;
// get the semaphore specific to this key
var keyLock = _keyLocks.GetOrAdd(key, x => new SemaphoreSlim(1));
await keyLock.WaitAsync();
try
{
// try to get value from cache
if (!_cache.TryGetValue(key, out cached) || IsExpiredDelete(cached, key))
{
//possible performance optimization: measure it before uncommenting
//keyLock.Release();
ToT value = await GetSomethingTheLongWayAsync(key);
DateTime creation = DateTime.Now;
// in case of performance optimization
// get the semaphore specific to this key
//keyLock = _keyLocks.GetOrAdd(key, x => new SemaphoreSlim(1));
//await keyLock.WaitAsync();
bool notFound;
if (notFound = !_cache.TryGetValue(key, out cached) || IsExpiredDelete(cached, key))
{
cached = new Tuple<ToT, DateTime>(value, creation);
_cache.TryAdd(key, cached);
}
else
{
if (!notFound && cached.Item2 < creation)
{
cached = new Tuple<ToT, DateTime>(value, creation);
_cache.TryAdd(key, cached);
}
}
}
}
finally
{
keyLock.Release();
}
return cached.Item1;
}
}
对于通用FromT
,IEqualityComparer
需要Dictionary
<强>用法/演示强>
private static async Task<string> GetSomethingTheLongWayAsync(int key)
{
await Task.Delay(15000);
Console.WriteLine("Long way for: " + key);
return key.ToString();
}
static void Main(string[] args)
{
Test().Wait();
}
private static async Task Test()
{
int key;
string val;
key = 1;
var cache = new Cached<int, string>(GetSomethingTheLongWayAsync, 1);
Console.WriteLine("getting " + key);
val = await cache.GetSomethingAsync(key);
Console.WriteLine("getting " + key + " resulted in " + val);
Console.WriteLine("getting " + key);
val = await cache.GetSomethingAsync(key);
Console.WriteLine("getting " + key + " resulted in " + val);
await Task.Delay(65000);
Console.WriteLine("getting " + key);
val = await cache.GetSomethingAsync(key);
Console.WriteLine("getting " + key + " resulted in " + val);
Console.ReadKey();
}
还有更高级的可能性,例如GetOrAdd的重载,它接受委托和Lazy对象,以确保只调用一次生成器函数(而不是信号量和锁)。
public class AsyncCache<FromT, ToT>
{
private Func<FromT, Task<ToT>> GetSomethingTheLongWayAsync;
public AsyncCache(Func<FromT, Task<ToT>> _GetSomethingTheLongWayAsync, int expiration_min)
{
GetSomethingTheLongWayAsync = _GetSomethingTheLongWayAsync;
Expiration = expiration_min;
}
int Expiration;
private ConcurrentDictionary<FromT, Tuple<Lazy<Task<ToT>>, DateTime>> _cache =
new ConcurrentDictionary<FromT, Tuple<Lazy<Task<ToT>>, DateTime>>();
private bool IsExpiredDelete(Tuple<Lazy<Task<ToT>>, DateTime> value, FromT key)
{
bool _is_exp = (DateTime.Now - value.Item2).TotalMinutes > Expiration;
if (_is_exp)
{
_cache.TryRemove(key, out value);
}
return _is_exp;
}
public async Task<ToT> GetSomethingAsync(FromT key)
{
var res = _cache.AddOrUpdate(key,
t => new Tuple<Lazy<Task<ToT>>, DateTime>(new Lazy<Task<ToT>>(
() => GetSomethingTheLongWayAsync(key)
)
, DateTime.Now) ,
(k,t) =>
{
if (IsExpiredDelete(t, k))
{
return new Tuple<Lazy<Task<ToT>>, DateTime>(new Lazy<Task<ToT>>(
() => GetSomethingTheLongWayAsync(k)
), DateTime.Now);
}
return t;
}
);
return await res.Item1.Value;
}
}
相同用法,只需替换AsyncCache
而不是Cached
。