.NET 8: MemoryCache SlidingExpiration без доступа

Мой вопрос похож на этот, но ответы объясняют только, почему все происходит именно так, а не как на самом деле достичь желаемого решения.

Прямо сейчас, определяя

var options = new MemoryCacheEntryOptions()
{
    // After x minutes of non-access, remove
    SlidingExpiration = TimeSpan.FromMinutes(x),
}
.RegisterPostEvictionCallback(OnCacheItemRemoved);

КэшItem будет удален только при доступе к нему в момент времени t1 > t0 + x'. Это означает, что если после этого момента я больше не получу доступ к элементу, он также никогда не будет удален из кеша.

Я мог бы установить абсолютный срок действия «да», но это отменяло бы скользящее окно. Если бы я хотел, чтобы он оставался в памяти, если к нему все еще обращаются, то он был бы выселен из-за абсолютного истечения срока действия. Мне нужно простое раздвижное окно с автоматическим выселением, если к элементу не обращались в течение x минут. Я ожидаю, что внутренний таймер справится с этим. Ему не нужно запускать каждую секунду из соображений производительности, мне достаточно одного раза в минуту.

Я тоже не вижу никакой конфигурации, как проверить выселение.

РЕДАКТИРОВАТЬ При добавлении MemoryCache для DI на самом деле есть опция ExpirationScanFrequency

services.AddMemoryCache(options => {
    // Check for expired cache items once a minute
    options.ExpirationScanFrequency = TimeSpan.FromMinutes(1);
});

но это, похоже, не работает. Мой обратный вызов все еще не вызывается несколько минут спустя, хотя к нему никто не обращается. X на данный момент установлен на 1.

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
52
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

КэшItem будет удален только при доступе к нему в определенный момент времени t1 > t0 + x'. Это означает, что если после этого момента я больше не получу доступ к элементу, он также никогда не будет удален из кеша.

Это не совсем верное утверждение. Как поясняется в ссылке формы одного из ответов, любое действие в кеше может привести к выселению, например добавление нового элемента в кеш или даже чтение другого ключа:

Мы не хотели, чтобы таймер всегда работал на незанятом сайте. Любое действие в кеше (получить, установить, удалить) может вызвать фоновое сканирование элементов с истекшим сроком действия. Таймер в CancellationTokenSource (CancelAfter) также удалит запись и запустит сканирование элементов с истекшим сроком действия.

См. использование StartScanForExpiredItemsIfNeeded в исходном коде — он вызывается в нескольких методах, таких как Remove, SetEntry, TryGetValue и EntryExpired.

Например, вы можете играть со следующим:

// some setup
var sc = new ServiceCollection();
sc.AddMemoryCache(opts =>
{
    opts.ExpirationScanFrequency = TimeSpan.FromMilliseconds(200);
});
var sp = sc.BuildServiceProvider();
var cache = sp.GetRequiredService<IMemoryCache>();
// eviction "debugger"
PostEvictionDelegate OnCacheItemRemoved = (key, value, reason, state) => Console.WriteLine($"{key} {value} {reason} {state}");
// entry to be evicted
var options = new MemoryCacheEntryOptions()
    {
        SlidingExpiration = TimeSpan.FromMilliseconds(100),
    }
    .RegisterPostEvictionCallback(OnCacheItemRemoved);
cache.Set("1", 1, options);
// some other entry
cache.Set("not1", 1);

for (int i = 0; i < 20; i++)
{
    await Task.Delay(50);
    Console.WriteLine(i);
    // uncommenting any of the following lines 
    // triggers expiration in the Release mode:

    // cache.Set("1" + i, 1, options);
    // cache.Get<int>("not1");
    // cache.TryGetValue("notPresent", out _);
}

Здесь вы мало что можете сделать, кроме какого-то хакерского подхода, например:

  • создание таймера и либо доступ к некоторому ключу (например, фиктивному), либо вызов MemoryCache.Compact вручную (т. е. что-то вроде (cache as MemoryCache)?.Compact(0.01)), когда он истекает
  • добавление некоторой фиктивной записи в кэше с абсолютным сроком действия (и чтение ее по истечении срока действия).
Ответ принят как подходящий

Ниже приведен пример кода, который запускает таймер для проверки элементов с истекшим сроком действия, что может удовлетворить ваши потребности.

Срок действия элементов будет удаляться из кеша каждые 5 секунд — вы можете изменить продолжительность времени, изменив две строки:

cache_opts.ExpirationScanFrequency = TimeSpan.FromSeconds( 5 );

cache_check_timer = new System.Timers.Timer( 5000 );

В примере создается кэш памяти и добавляются 3 элемента, срок действия каждого из которых истечет, если к нему не будет доступа в течение скользящего срока действия:

using Microsoft.Extensions.Caching.Memory;
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;

namespace InMemoryNetCore
{

   class Program
   {

      private static System.Timers.Timer? cache_check_timer;

      private static IMemoryCache? cache;

      public static void Main( string[] args )
      {

         String in_str;


         // Create a memory cache ...
         // Important - set the minimum length of time between successive scans for expired items (default is one minute) ...

         var cache_opts = new MemoryCacheOptions();

         cache_opts.ExpirationScanFrequency = TimeSpan.FromSeconds( 5 );

         cache = new MemoryCache( cache_opts );


         // Create a timer with a five second interval ...

         cache_check_timer = new System.Timers.Timer( 5000 );

         cache_check_timer.AutoReset = true;

         cache_check_timer.Elapsed += cache_check;

         cache_check_timer.Start();


         // Create cache item which executes call back function

         var cacheEntryOptions = new MemoryCacheEntryOptions()
             .SetPriority( Microsoft.Extensions.Caching.Memory.CacheItemPriority.Normal )
             .SetSlidingExpiration( TimeSpan.FromSeconds( 5 ) )
             .RegisterPostEvictionCallback( callback: cache_item_removed );


         // add cache Item with options of callback

         cache.Set( 1, "Cache Item 1", cacheEntryOptions );

         cache.Set( 2, "Cache Item 2", cacheEntryOptions );

         cache.Set( 3, "Cache Item 3", cacheEntryOptions );


         // Get user input to terminate program ...

         in_str = String.Empty;

         while ( !"QUIT".Equals( in_str ) )
         {

            Console.Write( "Enter QUIT to end: " );

            in_str = Console.ReadLine();

         }


         // Stop and dispose of timer ...

         cache_check_timer.Stop();
         
         cache_check_timer.Dispose();


         Console.WriteLine( "\nProgram ended" );

      }

      // Used to issue a Get request to the cache using a key value that we
      // know does not exist - only to invoke the internal scan for expired items ...
      private static void cache_check( object? sender, ElapsedEventArgs e )
      {

         //Console.WriteLine( "Here in cache_check" );
         //Console.WriteLine( "Timer event was raised at {0:HH:mm:ss.fff}", e.SignalTime );

         if ( cache is not null )
         {

            // Issue a Get for a key that does not exist ...

           _ = cache.Get( -1 );

         }

      }


      // Callback when cached items are removed ...
      private static void cache_item_removed( object key, object? value, EvictionReason reason, object? state )
      {

         Console.Write( "\nCache key: " + key + " removed from cache due to: " + reason );

      }

   }

}

Другие вопросы по теме

Похожие вопросы

Не удалось загрузить файл или сборку «System.Diagnostics.DiagnosticSource», версия = 6.0.0.0. Система не может найти указанный файл
Зачем использовать структуру для хранения констант, а не статический класс?
Невозможно получить доступ к наблюдаемому свойству в модели представления из соответствующего файла .xaml представления — .NET MAUI MVVM
Устойчивые функции Azure не вызываются при использовании с триггером Q с использованием управляемого удостоверения
Как я могу преобразовать этот SQL-запрос в linq, когда у меня есть подзапрос, группировка и предложение?
Проблема развертывания Blazor WASM Core в Azure
Преобразование оператора foreach в оператор выбора
Что происходит, когда несколько задач C# запускаются после завершения другой задачи?
ASP .NET Core — расширение @Html.EditorFor для добавления <span>
Netwonsoft JsonConvert.Deserialization выдает исключение JsonSerializationException: «Ошибка преобразования значения «Имя моего пользовательского объекта» в тип «System.Type»