异步/等待和缓存

时间:2014-04-03 06:46:02

标签: c# caching async-await

我的服务层正在缓存很多Db请求到memcached,这是否使得无法使用Async / Await?例如,我怎么能等待这个?

public virtual Store GetStoreByUsername(string username)
{
        return _cacheManager.Get(string.Format("Cache_Key_{0}", username), () =>
        {
                return _storeRepository.GetSingle(x => x.UserName == username);
        });
}

注意:如果密钥存在于缓存中,它将返回&#34; Store&#34; (不是Task<Store>),如果密钥在缓存中不存在,它将执行lambda。如果我将Func更改为

return await _storeRepository.GetSingleAsync(x => x.UserName == username);

方法签名

public virtual async Task<Store> GetStoreByUsername(string username)

由于缓存返回类型,这显然不起作用。

2 个答案:

答案 0 :(得分:15)

这是一种缓存异步操作结果的方法,可以保证没有缓存未命中并且是线程安全的。

在接受的答案中,如果在循环或多个线程中多次请求相同的用户名,则DB请求将一直发送,直到有缓存的响应,此时缓存将开始被使用。

下面的方法为每个唯一键创建一个SemaphoreSlim对象。这将阻止长时间运行的var is_prime = function(n) { var i; // <~~ declare i as local variable here if (n === 1) { 操作为同一个键运行多次,同时允许它同时针对不同的键运行。显然,有保留async对象的开销以防止缓存未命中,因此根据用例可能不值得。但是,如果保证没有缓存未命中很重要,那么这就完成了。

SemaphoreSlim

注意:为了进一步优化此方法,可以在锁定获取步骤之前执行额外的缓存检查。

答案 1 :(得分:7)

看起来缓存管理器执行所有“检查它是否存在,如果不运行lambda然后存储”。如果是这样,那么async的唯一方法是使用GetAsync方法返回Task<Store>而不是Store,即

public virtual Task<Store> GetStoreByUsernameAsync(string username)
{
    return _cacheManager.GetAsync(string.Format("Cache_Key_{0}", username), () =>
    {
        return _storeRepository.GetSingleAsync(x => x.UserName == username);
    });
}

请注意,由于我们未使用async,因此无需将其标记为await。然后缓存管理器会执行以下操作:

public async Task<Store> GetAsync(string key, Func<Task<Store>> func)
{
    var val = cache.Get(key);
    if(val == null)
    {
        val = await func().ConfigureAwait(false);
        cache.Set(key, val);
    }
    return val;
}