Custom ChangeMonitor for .Net MemoryCache causes invalid operation exception
Asked Answered
D

3

9

I have written my own custom change monitor class for the .NET MemoryCache. It seems to initialize fine, but when I attempt to add it to the Cache, it throws an InvalidOperation exception - The method has already been invoked, and can only be invoked once.

My change monitor class:

internal class MyChangeMonitor : ChangeMonitor
{
    private Timer _timer;
    private readonly string _uniqueId;
    private readonly TypeAsOf _typeAsOf;
    private readonly string _tableName;

    public GprsChangeMonitor(TypeAsOf typeAsOf, string tableName)
    {
        bool initComplete = false;
        try
        {
            _typeAsOf = typeAsOf;
            _tableName = tableName;

            _uniqueId = Guid.NewGuid().ToString();
            TimeSpan ts = new TimeSpan(0, 0, 5, 0, 0);
            _timer = new Timer {Interval = ts.TotalMilliseconds};
            _timer.Elapsed += CheckForChanges;
            _timer.Enabled = true;
            _timer.Start();
            initComplete = true;
        }
        finally 
        {
            base.InitializationComplete();
            if(!initComplete)
                Dispose(true);
        }
    }

    void CheckForChanges(object sender, System.Timers.ElapsedEventArgs e)
    {
        //check for changes, if different
        base.OnChanged(_typeAsOf);
    }
 }

The code I use to create the cache policy and add the key/value pair to the cache:

CacheItemPolicy policy = new CacheItemPolicy
{
    UpdateCallback = OnCacheEntryUpdateCallback
};

policy.AbsoluteExpiration = SystemTime.Today.AddHours(24);
//monitor the for changes
string tableName = QuickRefreshItems[type];
MyChangeMonitor cm = new MyChangeMonitor(typeAsOf, tableName);
policy.ChangeMonitors.Add(cm);
cm.NotifyOnChanged(OnRefreshQuickLoadCacheItems);

MyCache.Set(cacheKey, value, policy);

The Set call throws the invalid operation exception which is weird because, according to the MSDN documentation, it only throws the ArgumentNull, Argument, ArgumentOutOfRange, and NotSupported exceptions.

I am sure that I must be making a simple mistake. But it's hard to find good documentation or examples on writing your own custom change monitor. Any help would be appreciated.

Dit answered 1/4, 2011 at 19:37 Comment(6)
Can you attach a debugger, turn off Just My Code and then break on exceptions to see what the call stack is for the InvalidOperationException?Dioptric
The stack trace is not very helpful. It is in System.Runtime.Caching.ChangeMonitor.NotifyOnChanged(OnChangedCallback onChangedCallback), but it was not in my callback for NotifyOnChanged, because it isn't being called.Dit
Apparently, I have to add the change monitor to the policy AFTER I add the item to the cache. If I add it before, then I get the exception.Dit
@Dit Please mark this as the answer. I got stuck for way too long before reading this. Thanks!Amalburga
The documentation seems to suggest the opposite: msdn.microsoft.com/en-us/library/…Kape
Keith, >>Apparently, I have to add the change monitor to the policy AFTER I add the item to the cache<< - In this case ChangeMonitor will not fire change event notification - just tested it.Colure
H
6

I know the comments have the answer, but I wanted it to be more obvious...

When a ChangeMonitor is used, it will fire immediately if the cache entry does not exist.
MSDN documentation states it this way:

A monitored entry is considered to have changed for any of the following reasons:

A) The key does not exist at the time of the call to the CreateCacheEntryChangeMonitor method. In that case, the resulting CacheEntryChangeMonitor instance is immediately set to a changed state. This means that when code subsequently binds a change-notification callback, the callback is triggered immediately.

B) The associated cache entry was removed from the cache. This can occur if the entry is explicitly removed, if it expires, or if it is evicted to recover memory

Haas answered 24/8, 2012 at 16:21 Comment(0)
A
5

I've had the exact same error:

    Source: System.Runtime.Caching
    Exception type: System.InvalidOperationException
    Message: The method has already been invoked, and can only be invoked once.
    Stacktrace:    at System.Runtime.Caching.ChangeMonitor.NotifyOnChanged(OnChangedCallback onChangedCallback)
                   at System.Runtime.Caching.MemoryCacheEntry.CallNotifyOnChanged()
                   at System.Runtime.Caching.MemoryCacheStore.AddToCache(MemoryCacheEntry entry)
                   at System.Runtime.Caching.MemoryCacheStore.Set(MemoryCacheKey key, MemoryCacheEntry entry)
                   at System.Runtime.Caching.MemoryCache.Set(String key, Object value, CacheItemPolicy policy, String regionName)

I've searched for it for hours.. until the light of logic struck me:

I was using a static policy object that was reused.. (some unconscious process in me reuses all objects if they are equal,maybe I am afraid of constructing objects that consume some bytes in memory )

By creating a new policy object for every item in the cache, the error was gone. Pretty logical if you think about it.

Autobiographical answered 18/1, 2017 at 9:15 Comment(0)
N
2

Posting a late answer as I've just faced the same issue and conducted my own investigation.

When you register your change monitor with a cached item policy — policy.ChangeMonitors.Add(cm) — the CacheItemPolicy implementation registers its own change callback on it via ChangeMonitor.NotifyOnChanged. You're not supposed to be calling cm.NotifyOnChanged to register yet another callback, or it will throw The method has already been invoked, and can only be invoked once at that point.

Instead, use CacheItemPolicy.UpdateCallback or CacheItemPolicy.RemovedCallback to update/remove the cache item, e.g. as described in this blog post.

Nazler answered 25/7, 2017 at 20:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.