我遇到了一个看似简单的问题,但最终却失败了。我们有:
public class MyService
private readonly IMemoryCache cache;
private ConcurrentDictionary<int, bool> cacheKeys;
public MyController(IMemoryCache m) { cache = m; }
public UserModel GetUser(int userId) {
// Gets the user from the cache if possible, adds it if not
if (cache.TryGetValue(userId, out var result)) return result;
result = new UserModel(userId);
cache.Set(userId, result); // Absolute and Sliding expiration omitted for brevity
cacheKeys.Add(userId, true);
return result;
}
public void InvalidateCache() {
// Existing comment reads something along the lines of
// Since IMemoryCache has no clear, we do this
foreach (var entry in cacheKeys.ToList())
cache.Remove(entry.Key);
}
// RegisterCacheEvictionHandler omitted for brevity
}
该服务由
AddSingleton()
添加到依赖注入,并且 IMemoryCache
中没有引用 MemoryCache
或 Startup.cs
。
所以问题是多方面的。
IMemoryCache
和添加到ConcurrentDictionary
可以分开,导致字典与缓存过时。这可能会导致按键在 ConcurrentDictionary
中堆积。MemoryCache.Clear()
会破坏 IMemoryCache
的其他用户。其他用户之一在缓存中有不可驱逐的对象。Guid
键意外运行的。MemoryCache
意味着什么。(MemoryCache 是 ASP.NET Core 版本,不是向后兼容版本)
使 MemoryCache 中的一组已知对象失效的能力几乎是内置的;只是不是以一种容易被发现的方式。
如果我们这样做:
private readonly IMemoryCache cache;
private Tuple<MemoryCacheEntryOptions, CancellationTokenSource> options;
public UserService(MemoryCache m) {
cache = m;
options = CreateOptions();
}
private Tuple<MemoryCacheEntryOptions, CancellationTokenSource> CreateOptions() {
var source = new CancellationTokenSource();
var options = new MemoryCacheEntryOptions()
.SetSlidingExpiration(new TimeSpan(...))
.SetAbsoluteExpiration(new TimeSpan(...))
// This is the heart of the solution. We can signal immediate expiration of a set of objects with one call.
.AddExpirationToken(new CancellationChangeToken(source.Token));
return Tuple.Create<options, source>();
}
public UserModel GetUser(int userId) {
// Gets the user from the cache if possible, adds it if not
if (cache.TryGetValue(userId, out var result)) return result;
var cacheoptions = options.Item1;
result = new UserModel(userId);
cache.Set(userId, result, cacheoptions);
return result;
}
public void InvalidateCache() {
var newOptions = CreateOptions();
Interlocked.Exchange(ref options, newOptions);
newOptions.Item2.Cancel();
newOptions.Item2.Dispose();
}
然后
InvalidateCache()
无需锁定即可工作。由于没有第二个密钥集合,因此不会失去同步。这与其自身的缓存大小限制正确交互;只能重新访问 .Set() 调用。