3

我一直在使用 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),并且我在更新和删除回调时执行处置。

添加了那些lockonPubSub监视器以保证我们在base.OnChange().

所有项目都从缓存中删除,并且它们的监视器被释放(从我能够分析的文档和源代码中),但不知何故有监视器没有被释放,或者某些缓存项目UpdateCallback/RemoveCallback没有被调用,因为文件更改事件仍在引发.

谢谢大家

4

0 回答 0