我一直在使用 System.Runtime.Caching.MemoryCache 并且在处理监视器方面遇到了一些麻烦。所以一点上下文,我正在尝试实现一个缓存,其中项目通过使用基于 PubSub 架构的自定义ChangeMonitor
(不,我不想使用)因文件更改而失效。HostFileChangeMonitor
我在 MemoryCache 上有 2 种类型的插入:
一次插入,应该在监视器更改或到期时删除:
var monitor = new PubSubMonitor(cacheKey, subscription); policy.ChangeMonitors.Add(monitor); policy.SlidingExpiration = slidingSpan; policy.AbsoluteExpiration = expirationDate; policy.Priority = System.Runtime.Caching.CacheItemPriority.NotRemovable; policy.RemovedCallback = (args) => { if (subscription != null) { subscription.Dispose(); subscription = null; } };
万岁插入,应该在监视器更改或到期时重新添加:
var monitor = new PubSubMonitor(cacheKey, subscription); policy.ChangeMonitors.Add(monitor); policy.SlidingExpiration = ObjectCache.NoSlidingExpiration; policy.AbsoluteExpiration = DateTime.UtcNow.AddDays(1); policy.Priority = System.Runtime.Caching.CacheItemPriority.NotRemovable; policy.UpdateCallback = EncapsulateListener(cacheKey, invalidationCallback, subscription);
这里是实现EncapsulateListenerCallback
:
private CacheEntryUpdateCallback EncapsulateListenerCallback(string originalKey, CacheItemInvalidationCallback invalidationCallback, ISubscription subscription) {
return (args) => {
if (args.RemovedReason == CacheEntryRemovedReason.ChangeMonitorChanged || args.RemovedReason == CacheEntryRemovedReason.Expired) {
args.UpdatedCacheItem = new CacheItem(originalKey, invalidationCallback());
var newPolicy = new CacheItemPolicy();
{ //configurations
var newMonitor = new PubSubMonitor(originalKey, subscription);
newPolicy.ChangeMonitors.Add(newMonitor);
newPolicy.SlidingExpiration = ObjectCache.NoSlidingExpiration;
newPolicy.AbsoluteExpiration = DateTime.UtcNow.AddDays(1);
newPolicy.Priority = System.Runtime.Caching.CacheItemPriority.NotRemovable;
newPolicy.UpdateCallback = EncapsulateListenerCallback(originalKey, invalidationCallback, subscription);
}
args.UpdatedCacheItemPolicy = newPolicy;
} else {
if (subscription != null) {
subscription.Dispose();
subscription = null;
}
}
};
}
还有PubSubMonitor
:
public class PubSubMonitor : ChangeMonitor {
private readonly string topic;
private readonly ISubscription subscription;
private volatile bool monitorDisposed = false;
public PubSubMonitor(string topic, ISubscription subscription) : base() {
bool initialized = false;
try {
this.topic = topic;
this.subscription = subscription;
this.subscription.OnMessage += this.InnerOnChange;
initialized = true;
} finally {
InitializationComplete();
if (!initialized) {
Dispose();
}
}
}
private void InnerOnChange(object state) {
lock (this) {
if (!monitorDisposed) {
this.subscription.OnMessage -= this.InnerOnChange;
try {
base.OnChanged(state);
} catch (Exception ex) {
//log the error
}
}
}
}
public override string UniqueId => topic + "[" + Guid.NewGuid().ToString() + "]";
protected override void Dispose(bool disposing) {
lock (this) {
this.subscription.OnMessage -= this.InnerOnChange;
monitorDisposed = true;
}
}
}
ISubscription
对象将调用在OnMessage
事件上注册的回调以通知更改。
我对这个实现的问题是,有时,当我的应用程序正在卸载时,这个堆栈System.NullReferenceException
会抛出一个:MemoryCache
at System.Runtime.Caching.MemoryCacheStore.RemoveFromCache(System.Runtime.Caching.MemoryCacheEntry, System.Runtime.Caching.CacheEntryRemovedReason, Boolean)
at System.Runtime.Caching.MemoryCacheStore.Remove(System.Runtime.Caching.MemoryCacheKey, System.Runtime.Caching.MemoryCacheEntry, System.Runtime.Caching.CacheEntryRemovedReason)
at System.Runtime.Caching.MemoryCacheEntry.OnDependencyChanged(System.Object)
at System.Runtime.Caching.ChangeMonitor.OnChangedHelper(System.Object)
at System.Runtime.Caching.ChangeMonitor.OnChanged(System.Object)
at MyNamespace.PubSubMonitor.InnerOnChange(System.Object)
at MyNamespace.FileBasedPubSubSubscription.RaiseOnMessage(MyNamespace.PubSubMessage)
at MyNamespace.FileBasedPubSubSubscription.OnFileEvent(System.String, System.Object, System.IO.FileSystemEventArgs)
at MyNamespace.FileBasedPubSubSubscription+<>c__DisplayClass10_0.<SubscribeTopic>b__0(System.Object, System.IO.FileSystemEventArgs)
at System.IO.FileSystemWatcher.OnChanged(System.IO.FileSystemEventArgs)
at System.IO.FileSystemWatcher.CompletionStatusChanged(UInt32, UInt32, System.Threading.NativeOverlapped*)
at System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32, UInt32, System.Threading.NativeOverlapped*)
从我能够使用 DotPeek 进行故障排除的是,即使 MemoryCache 对象已经被释放,我的订阅也会引发 OnMessage。该订阅在处置时停止引发事件(通过处置和禁用引发事件 on FileSystemWatcher
),并且我在更新和删除回调时执行处置。
添加了那些lock
onPubSub
监视器以保证我们在base.OnChange()
.
所有项目都从缓存中删除,并且它们的监视器被释放(从我能够分析的文档和源代码中),但不知何故有监视器没有被释放,或者某些缓存项目UpdateCallback
/RemoveCallback
没有被调用,因为文件更改事件仍在引发.
谢谢大家