3
\$\begingroup\$

I have attempted to implement the advice from the original post and debugged the original concept.

Certain changes in the logic have necessitated other changes, including breaking into the library class and the test console app.

Test console:

using System;

namespace LongCacheTest
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var cacher = new Cacher.Cacher<string>();

            cacher.Add("first", SimulateSlowLoadingContentRetrieval);
            cacher.Add("second", SimulateSlowLoadingContentRetrieval);

            Console.ReadLine();
        }

        private static string SimulateSlowLoadingContentRetrieval(string key)
        {
            var end = DateTime.Now.AddSeconds(10);

            while (DateTime.Now < end) ;

            return $"content:{key}";
        }
    }
}

Library class:

using System;
using System.Runtime.Caching;
using System.Threading.Tasks;

namespace Cacher
{
    public class Cacher<T>
    {
        public delegate T CacheEntryGet(string key);
        private MemoryCache Cache { get; set; } = new MemoryCache("long");

        private CacheItemPolicy ImmediatePolicy => new CacheItemPolicy
        {
            AbsoluteExpiration = new DateTimeOffset(DateTime.Now.AddSeconds(3)),
            Priority = CacheItemPriority.Default,
            RemovedCallback = DidRemove
        };

        private CacheItemPolicy RegularPolicy => new CacheItemPolicy
        {
            AbsoluteExpiration = new DateTimeOffset(DateTime.Now.AddSeconds(20)),
            Priority = CacheItemPriority.Default,
            RemovedCallback = DidRemove
        };

        private CacheItemPolicy NoRemovePolicy => new CacheItemPolicy
        {
            Priority = CacheItemPriority.NotRemovable
        };

        private async Task GetFreshContent(CacheEntryRemovedArguments args)
        {
            await Task.Run(() =>
            {
                Cache.Set(args.CacheItem, NoRemovePolicy);

                var item = args.CacheItem.Value as Cacheable<T>;

                item.Obj = item.Getter(args.CacheItem.Key);

                var policy = RegularPolicy;

                Cache.Set(args.CacheItem, policy);
            });
        }

        private void DidRemove(CacheEntryRemovedArguments args)
        {
            GetFreshContent(args);
        }

        public void Add(string key, CacheEntryGet getter)
        {
            var item = new Cacheable<T>
            {
                Getter = getter
            };

            Cache.Set(key, item, ImmediatePolicy);
        }

        public T Get(string key) => (Cache[key] as Cacheable<T>).Obj;
    }
}

Supporting library class:

namespace Cacher
{
    internal class Cacheable<T>
    {
        public T Obj { get; set; }
        public Cacher<T>.CacheEntryGet Getter { get; set; }
    }
}

Answers to questions:

Q: In what context is this class meant to be used?
A: From start up of a ASP.NET web site, throughout the running life cycle. The aim is to provide a cache of data which can be refreshed, using the expiration mechanism to fire the refresh process.

Q: Why does Add only invoke the given getter after 3 seconds, and why does it not accept an initial value?
A: Essentially, for testing, right now. In practice, because I found that a lower value actually causes the getter to be called after a much longer wait. This is relevant to a SO question.

Q: Why switch to a 20-second expiration time after the initial 3 seconds?
A: The initial expiration is attempting to force the firing of the getter for the first time. Subsequent firings will be to refresh the data after much longer time periods, but testing is for 20 second bouts. Ideally, it would be 0 seconds and 1 hour.

Q Why are entries perpetually being refreshed every 20 seconds?
A: See above answer - this would, in practice, be an hour or so. It is simulating the lifetime of the cached object, but in a short-lived console for PoC testing.

Q: Why is there no eviction policy?
A: An item expires and thus needs refreshing. While an entry is being refreshed I add it back in without an expiry to give the long-running getter time to work. The new item then replaces the non-expiring item.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ In what context is this class meant to be used? Why does Add only invoke the given getter after 3 seconds, and why does it not accept an initial value? Why switch to a 20-second expiration time after the initial 3 seconds? Why are entries perpetually being refreshed every 20 seconds? Why is there no eviction policy? \$\endgroup\$ Jan 24, 2019 at 12:42
  • 1
    \$\begingroup\$ For internal timings, you should use DateTime.UtcNow instead of DateTime.Now. \$\endgroup\$
    – Rick Davin
    Jan 24, 2019 at 13:06
  • \$\begingroup\$ @pieter-witvoet I have added answers to your questions in the post. \$\endgroup\$
    – Matt W
    Jan 24, 2019 at 13:15

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.