MemoryCache似乎不会处置所有监视器

时间:2018-04-20 14:17:57

标签: c# synchronization memorycache domainunload

我一直在使用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}没有被调用,因为文件更改事件仍然被引发。

全部谢谢

0 个答案:

没有答案