我一直在使用System.Runtime.Caching.MemoryCache,我在监视器处理方面遇到了一些麻烦。
所以一点上下文,我正在尝试使用自定义ChangeMonitor
实现一个缓存,其中项目从文件的更改中失效(不,我不想使用HostFileChangeMonitor
) ,基于PubSub架构。
我在MemoryCache上有两种类型的插入:
一次插入,应该在监视器更改或到期时删除:
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。该订阅在处理时停止引发事件(通过处置和禁用在FileSystemWatcher
上引发事件),并在更新时删除并删除回调。
添加lock
监视器上的PubSub
以确保在我们base.OnChange()
上时不会调用dispose方法。
所有项目都从缓存中移除,并且他们的监视器被处理掉(从我能够分析的文档和源代码中),但不知何故有监视器没有被处理,或者某些缓存项UpdateCallback
/ {{1}没有被调用,因为文件更改事件仍然被引发。
全部谢谢