异步/等待和缓存

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

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

注意:如果密钥存在于缓存中,它将返回“Store”(而不是Task ),如果缓存中不存在该密钥,则它将执行lambda。 如果我将Func更改为

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

并且方法签名为

 public virtual async Task GetStoreByUsername(string username) 

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

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

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

请注意,这不需要标记为async因为我们没有使用await 。 然后缓存管理器将执行以下操作:

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

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

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

下面的方法为每个唯一键创建一个SemaphoreSlim对象。 这将防止长时间运行的async操作对同一个键运行多次,同时允许它同时针对不同的键运行。 显然,保留SemaphoreSlim对象以防止缓存未命中的开销很大,因此根据用例可能不值得。 但是,如果保证没有缓存未命中很重要,那么这就完成了。

 private readonly ConcurrentDictionary _keyLocks = new ConcurrentDictionary(); private readonly ConcurrentDictionary _cache = new ConcurrentDictionary(); public async Task GetStoreByUsernameAsync(string username) { Store value; // get the semaphore specific to this username var keyLock = _keyLocks.GetOrAdd(username, x => new SemaphoreSlim(1)); await keyLock.WaitAsync().ConfigureAwait(false); try { // try to get Store from cache if (!_cache.TryGetValue(username, out value)) { // if value isn't cached, get it from the DB asynchronously value = await _storeRepository.GetSingleAsync(x => x.UserName == username).ConfigureAwait(false); // cache value _cache.TryAdd(username, value); } } finally { keyLock.Release(); } return value; } 

注意:要进一步优化此方法,可以在锁定获取步骤之前执行其他高速缓存检查。