Пользовательский ChangeMonitor для .Net MemoryCache вызывает исключение недопустимой операции

Я написал свой собственный класс монитора изменений для .NET MemoryCache. Кажется, он инициализируется нормально, но когда я пытаюсь добавить его в кеш, он выдает исключение InvalidOperation - The method has already been invoked, and can only be invoked once.

Мой класс монитора изменений:

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);
    }
 }

Код, который я использую для создания политики кеша и добавления пары ключ/значение в кеш:

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);

Вызов Set выдает исключение недопустимой операции, что странно, поскольку, согласно документации MSDN, он выдает только исключения ArgumentNull, Argument, ArgumentOutOfRange и NotSupported.

Я уверен, что совершаю простую ошибку. Но трудно найти хорошую документацию или примеры по написанию собственного монитора изменений. Любая помощь будет оценена по достоинству.


person Keith    schedule 01.04.2011    source источник
comment
Можете ли вы подключить отладчик, отключить «Только мой код», а затем разбить исключения, чтобы увидеть, какой стек вызовов для InvalidOperationException?   -  person Colin Thomsen    schedule 02.04.2011
comment
Трассировка стека не очень полезна. Он находится в System.Runtime.Caching.ChangeMonitor.NotifyOnChanged(OnChangedCallback onChangedCallback), но его не было в моем обратном вызове для NotifyOnChanged, поскольку он не вызывается.   -  person Keith    schedule 04.04.2011
comment
Судя по всему, я должен добавить монитор изменений в политику ПОСЛЕ того, как я добавлю элемент в кеш. Если я добавлю его раньше, я получу исключение.   -  person Keith    schedule 04.04.2011
comment
@Keith Пожалуйста, отметьте это как ответ. Я застрял слишком долго, прежде чем прочитать это. Спасибо!   -  person DougJones    schedule 01.09.2011
comment
Документация, похоже, предполагает обратное: msdn. microsoft.com/en-us/library/   -  person Jeff    schedule 12.07.2012
comment
Кейт, ››Очевидно, я должен добавить монитор изменений в политику ПОСЛЕ того, как я добавлю элемент в кеш ‹‹ — в этом случае ChangeMonitor не будет запускать уведомление о событии изменения — просто протестировал его.   -  person Philipp Munin    schedule 15.05.2014


Ответы (3)


Я знаю, что в комментариях есть ответ, но я хотел, чтобы он был более очевидным...

При использовании ChangeMonitor он срабатывает немедленно, если запись в кэше не существует.
MSDN в документации это указано так:

Отслеживаемая запись считается измененной по любой из следующих причин:

A) Ключ не существует во время вызова метода CreateCacheEntryChangeMonitor. В этом случае результирующий экземпляр CacheEntryChangeMonitor немедленно устанавливается в измененное состояние. Это означает, что когда код впоследствии связывает обратный вызов уведомления об изменении, обратный вызов запускается немедленно.

Б) Соответствующая запись кэша была удалена из кэша. Это может произойти, если запись удалена явным образом, если срок ее действия истек или если она вытеснена для восстановления памяти.

person EBarr    schedule 24.08.2012

У меня была точно такая же ошибка:

    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)

Я искал его часами... пока меня не осенил свет логики:

Я использовал статический объект политики, который был повторно использован.. (какой-то бессознательный процесс во мне повторно использует все объекты, если они равны, может быть, я боюсь создавать объекты, которые потребляют несколько байтов в памяти)

При создании нового объекта политики для каждого элемента в кеше ошибка исчезла. Довольно логично, если подумать.

person JDC    schedule 18.01.2017

Публикация позднего ответа, так как я только что столкнулся с той же проблемой и провел собственное расследование.

Когда вы регистрируете свой монитор изменений с помощью кэшированной политики элемента — policy.ChangeMonitors.Add(cm) — реализация CacheItemPolicy регистрирует на нем свой собственный обратный вызов изменения через ChangeMonitor.NotifyOnChanged. Вы не должны вызывать cm.NotifyOnChanged для регистрации еще одного обратного вызова, иначе в этот момент он выдаст The method has already been invoked, and can only be invoked once.

Вместо этого используйте CacheItemPolicy.UpdateCallback или CacheItemPolicy.RemovedCallback для обновления/удаления элемента кеша, например как описано в этой записи блога.

person noseratio    schedule 25.07.2017