Мой вопрос похож на этот, но ответы объясняют только, почему все происходит именно так, а не как на самом деле достичь желаемого решения.
Прямо сейчас, определяя
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
.
Кэш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 _);
}
Здесь вы мало что можете сделать, кроме какого-то хакерского подхода, например:
(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 );
}
}
}