0

I am trying to refresh IMemoryCache programmatically. After researching a few links about Eviction Calback and Clearing cache, I thought I could combine the strategies i.e. clear the cache which would cause the eviction callback to fire. However apparently the post eviction callback won't trigger when the cache is cleared using reflection because it seems the whole cache item with its options (that includes the callback ) is gone. (cache item count goes to 0)

So my question is about refreshing a cache item before expiration, as this issue is still open

    private static Dictionary<string, CancellationTokenSource> tokenDict = new Dictionary<string, CancellationTokenSource>();
    private MemoryCacheEntryOptions CacheOptions
    {
        get
        {
            var expirationToken = new CancellationChangeToken( new CancellationTokenSource(TimeSpan.FromMinutes(ExpirationMinutes + .01)).Token);
            var options = new MemoryCacheEntryOptions()
             // Do not remove due to memory pressure 
             .SetPriority(Microsoft.Extensions.Caching.Memory.CacheItemPriority.NeverRemove)
             .SetSlidingExpiration(TimeSpan.FromMinutes(ExpirationMinutes))
             // Force eviction to run AT expriry, default eviction happens when item is requested after expiry
             .AddExpirationToken(expirationToken)
             .RegisterPostEvictionCallback(callback: CacheItemRemoved, state: this);
             tokenDict[cacheKey] = cancellationTokenSource;
             return options;
        }
    }
    private void CacheItemRemoved(object key, object value, EvictionReason reason, object state)
    {
        _logger.LogInformation($"Reloading {key} cache upon eviction");
        switch (key)
        {
            case AccountCacheKey:
                GetAccountCacheAsync();
                break;
            case FundCacheKey:
                GetFundCacheAsync();
                break;
            default:
                break;
        }
    }
    private async Task<List<Account>> GetAccountCacheAsync()
    {
        return await _cache.GetOrCreateAsync(AccountCacheKey, async entry =>
        {
            entry.SetOptions(CacheOptions);
            var accounts = await LoadAccountsAsync().ConfigureAwait(false);
            return accounts;
        }).ConfigureAwait(false);
    }

    private async Task<List<Fund>> GetFundCacheAsync()
    {
        return await _cache.GetOrCreateAsync(FundCacheKey, async entry =>
        {
            entry.SetOptions(CacheOptions);
            var funds = await LoadFundsAsync().ConfigureAwait(false);
            return funds;
        }).ConfigureAwait(false);
    }
    public async Task RefreshCacheAsync()
    {
        var cacheKeys = new List<string> { AccountCacheKey, FundCacheKey };
        foreach (var key in cacheKeys)
        {
            if (tokenDict.TryGetValue(key, out var token))
            {
                if (token != null && !token.IsCancellationRequested && token.Token.CanBeCanceled)
                {
                    token.Cancel();
                    token.Dispose();
                }
            }
        }
    }
Anand
  • 1,387
  • 2
  • 26
  • 48

1 Answers1

2

You already posted a link with the best approach, but you seem to have chosen to go with one of the lower rated answers, which actually doesn't work for your purposes. Instead, you should follow this answer. It creates a cache "manager" class that among other things employs CancellationTokenSource to handle the eviction. That's actually the same method that was recommended in the Github issue you linked, as well.

Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • I am sorry, I was not clear enough in my question - I do not want to dump the whole cache - i want to force refresh specific entry – Anand Apr 17 '19 at 16:44
  • I solved the issue by having a dictionary of tokens keyed by the cache key instead of using the static token in the referenced code – Anand Apr 17 '19 at 18:01