none
System.runtime caching on the desktop

    Question

  • I have been using entlib caching application block on a windows client. Entlib caching has become a legacy app since the inclusion of caching in runtime. I am trying to migrate my code because it seems the right thing to do, but also because unlike Entlib, the runtime version allows me the opportunity to update cache items before they are evicted from the cache. Unfortunately I can't make it work.

    I am using MemoryCache.

    1. MemoryCache.Add throws when policy includes an update callback. It says UpdateCallback must be null. Must be a bug. MemoryCache.Set works.

    2. SlidingExpiration does not slide - it is absolute.

    3. As I read the docs, the update callback method can replace the item to be removed by setting the UpdatedCacheItem property, but it doesn't

    4. The fact there is no documentation does not help and is inexcusable.

    Am I looking at this in completely the wrong way?

    Here is a test rig:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Runtime.Caching;
    using System.Threading;
    
    namespace CachingTest
    {
      class Program
      {
        static MemoryCache cache;
    
        static void Main(string[] args)
        {
          Console.WriteLine("Starting app ...");
          cache = MemoryCache.Default;
    
          CacheItemPolicy p = new CacheItemPolicy();
          p.SlidingExpiration = TimeSpan.FromSeconds(30);
          p.UpdateCallback = new CacheEntryUpdateCallback(UpdateHandler);
    
          cache.Set(new CacheItem("A", "This is item A."), p);
    
    
          p = new CacheItemPolicy();
          p.SlidingExpiration = TimeSpan.FromSeconds(30);
          p.UpdateCallback = new CacheEntryUpdateCallback(UpdateHandler);
    
          // Uncomment to throw exception
          //cache.Add(new CacheItem("B", "This is item B."), p);
          cache.Set(new CacheItem("B", "This is item B."), p);
          
          Thread t = new Thread(delegate()
            {
              while (true)
              {
                Thread.Sleep(TimeSpan.FromSeconds(15));
                Console.WriteLine("Getting item A ...");
                
                CacheItem item = cache.GetCacheItem("A");
    
                if (item == null)
                {
                  throw new InvalidOperationException("Cache is null");
                }
                else
                {
                  Console.WriteLine("Got it");
                }
                
              }
            });
    
          t.Start();
    
          Console.WriteLine("Press any key to exit");
          Console.Read();
    
        }
    
        static void UpdateHandler(CacheEntryUpdateArguments args)
        {
          Console.WriteLine(args.Key + " about to be removed.");
    
          CacheItem item = cache.GetCacheItem(args.Key);
    
          CacheItem item2 = new CacheItem(args.Key, item.Value);
    
          // Should replace the expiring item? but it doesn't
          args.UpdatedCacheItem = item2;
          
        }
      }
    }
    

     


    Dick Page
    Monday, October 11, 2010 12:14 PM

Answers

  • I have made an update to the MSDN reference topic: CacheEntryUpdateArguments.UpdatedCacheItem Property. The update will be included with the reference topic when the next MSDN update is made. For now, I have included the updated information as community content below the reference information of this topic. Here are the details:

    If you want to exchange a cache entry that is about to be removed for an updated cache entry, you must assign a System.Runtime.Caching.CacheItem object to the System.Runtime.Caching.CacheEntryUpdateArguments.UpdatedCacheItem property and also assign a System.Runtime.Caching.CacheItemPolicy object to the System.Runtime.Caching.CacheEntryUpdateArguments.UpdatedCacheItemPolicy property. The System.Runtime.Caching.CacheItem value must be a value other than null. Cache implementations will interpret a null value for the System.Runtime.Caching.CacheEntryUpdateArguments.UpdatedCacheItem property as a notice that the current cache entry should be removed but not replaced.

    -- Erik Reitan
    Web Platform and Tools Developer Content
    This posting is provided "AS IS" with no warranties, and confers no rights.

     

     

    • Marked as answer by dickP Friday, February 11, 2011 10:09 AM
    Thursday, February 10, 2011 10:16 PM

All replies

  •  

    Hi,

     

    MemoryCache's Add and AddOrGetExisting method overloads do not support the UpdateCallback property. Therefore, to set the UpdateCallback property for a cache entry, use the Set method overloads instead, see more details from this doc.

     

    However, I think I reproduced what you mentioned in item2 and 3, you can submit them to Microsoft Connect feedback portal http://connect.microsoft.com, Microsoft engineers will evaluate them seriously, thanks. If this issue is urgent, please contact support at http://support.microsoft.com.


    Sincerely,
    Eric
    MSDN Subscriber Support in Forum
    If you have any feedback of our support, please contact msdnmg@microsoft.com.
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    Welcome to the All-In-One Code Framework! If you have any feedback, please tell us.
    Tuesday, October 12, 2010 5:59 AM
  • OK Will do

    - I missed the documentation about add vs. set. Sorry about that.

    - I am not clear about how CacheEntryUpdateCallback is supposed to work. I seems to invite the consumer to set the UpdatedCacheItem property of the CacheEntryUpdateArguments with an updated item if required. It feels unintuitive to do this by setting a property in the eventargs, although this pattern is used elsewhere in the framework (can't remember where) and I have used it myself. My assumption is that the new item will replace the old item atomically, so that other threads will not suffer a "not found" condition. I suppose  it might also avoid  some issues with manually deleting an item already flagged for removal and inserting a replacement? And another thing, is it valid to set the  UpdatedCacheItem to reference the expired item?

     

    Thanks


    Dick Page
    Tuesday, October 12, 2010 7:41 AM
  • In your UpdateHandler, in addition to

    args.UpdatedCacheItem = item2;

    to update a cache item, you should also set args.UpdatedCacheItemPolicy.

    Wednesday, January 05, 2011 12:21 AM
  • I have made an update to the MSDN reference topic: CacheEntryUpdateArguments.UpdatedCacheItem Property. The update will be included with the reference topic when the next MSDN update is made. For now, I have included the updated information as community content below the reference information of this topic. Here are the details:

    If you want to exchange a cache entry that is about to be removed for an updated cache entry, you must assign a System.Runtime.Caching.CacheItem object to the System.Runtime.Caching.CacheEntryUpdateArguments.UpdatedCacheItem property and also assign a System.Runtime.Caching.CacheItemPolicy object to the System.Runtime.Caching.CacheEntryUpdateArguments.UpdatedCacheItemPolicy property. The System.Runtime.Caching.CacheItem value must be a value other than null. Cache implementations will interpret a null value for the System.Runtime.Caching.CacheEntryUpdateArguments.UpdatedCacheItem property as a notice that the current cache entry should be removed but not replaced.

    -- Erik Reitan
    Web Platform and Tools Developer Content
    This posting is provided "AS IS" with no warranties, and confers no rights.

     

     

    • Marked as answer by dickP Friday, February 11, 2011 10:09 AM
    Thursday, February 10, 2011 10:16 PM
  • Thanks Erik

    I think I have also confirmed that the update handler is thread-safe. A cache client will still be able to retrieve the existing item until the replacement item is specified and the handler exits. I have also established that it is ok to set the updated item with the same instance as the expiring item. Here is my updated test rig:

      class Program
      {
        static MemoryCache cache;
    
        static void Main(string[] args)
        {
    
          Stopwatch sw = new Stopwatch();
          Console.WriteLine("Starting app ...");
          cache = MemoryCache.Default;
          sw.Start();
    
          CacheItemPolicy p = CreatePolicy();
    
          cache.Set(new CacheItem("A", "This is item A."), p);
    
          p = CreatePolicy();
    
          // Uncomment to throw exception
          //cache.Add(new CacheItem("B", "This is item B."), p);
          cache.Set(new CacheItem("B", "This is item B."), p);
          
          Thread t = new Thread(delegate()
            {
              while (true)
              {
                Thread.Sleep(TimeSpan.FromSeconds(15));
                Console.WriteLine("{0} @ {1} secs. Getting item A ...", Thread.CurrentThread.ManagedThreadId, sw.Elapsed.TotalSeconds);
                
                CacheItem item = cache.GetCacheItem("A");
    
                if (item == null)
                {
                  throw new InvalidOperationException("Cache is null");
                }
                else
                {
                  Console.WriteLine("{0} @ {1} secs. Got it", Thread.CurrentThread.ManagedThreadId, sw.Elapsed.TotalSeconds);
                }
                
              }
            });
    
          t.Start();
    
          Console.WriteLine("Press any key to exit");
          Console.Read();
    
        }
    
        private static CacheItemPolicy CreatePolicy()
        {
          CacheItemPolicy p = new CacheItemPolicy();
          p.SlidingExpiration = TimeSpan.FromSeconds(30);
          p.UpdateCallback = new CacheEntryUpdateCallback(UpdateHandler);
          return p;
        }
    
        static void UpdateHandler(CacheEntryUpdateArguments args)
        {
          // Seems to be thread-safe
          // the current item is not updated until the handler completes.
          
    
          Console.WriteLine("{0} {1} about to be removed. Sleeping", Thread.CurrentThread.ManagedThreadId, args.Key);
          
          Thread.Sleep(TimeSpan.FromSeconds(15));
          // >>>> existing item is retrieved if cache is accessed on another thread here.
          Console.WriteLine("Awake!");
    
          CacheItem item = cache.GetCacheItem(args.Key);
          
          // don't need to create a new item
          //CacheItem item2 = new CacheItem(args.Key, item.Value); 
    
          args.UpdatedCacheItem = item;
          
          // must set. will not replace expiring item otherwise
          args.UpdatedCacheItemPolicy = CreatePolicy(); // added after forum reply 5/1/11
        }
      }
    

    Dick Page
    Friday, February 11, 2011 10:19 AM
  • There doesn't seem to be a way to get CacheItemPolicy from CacheItem. So if I want to exchange a cache entry in Updatecallback, how would find out what the original policy for the expiring item is so that I can set it back onto the updated item?
    Wednesday, February 16, 2011 8:23 PM
  • As you see from my example I just recreated the CacheItemPolicy by calling a static method. I could not find a way of getting the original policy. This could be a real drag in many scenarios.


    Dick Page
    Thursday, February 17, 2011 7:30 AM
  • Agreed. It looks like I have to maintain a dictionary of key, policy pairs to utilize UpdateCallback.
    Thursday, February 17, 2011 6:29 PM
  • How about deriving a class from CacheItem with a property to hold the policy object?
    Dick Page
    Thursday, February 17, 2011 6:34 PM
  • Nope, MemoryCache.GetCacheItem returns a new CacheItem....

     

    public override CacheItem GetCacheItem(string key, [Optional, DefaultParameterValue(null)] string regionName)

    {

    ...

    ...

    return new CacheItem(key, obj2);

    }

    Friday, February 18, 2011 3:06 PM
  • I haven't tested it but I was thinking along the lines below.

    Alternatively define a custom type without deriving from CacheItem and put in the CacheItem payload.

    public class MyCacheItem : CacheItem
    {
        public CacheItemPolicy {get; set; }
    }
    
     ***
    
    MyCacheItem item new MyCacheItem("A", "This is item A."), 
    item.Policy = p;
    
    cache.Set(item, p);
    
    ***
    
    MyCacheItem item = cache.GetCacheItem(args.Key) as MyCacheItem;
    
    

    Dick Page
    Monday, February 21, 2011 10:00 AM
  • Yup that would be ideal. The problem is this line: 

     

     

    MyCacheItem item = cache.GetCacheItem(args.Key) as MyCacheItem;

    using reflector, I found out cache.GetCacheItem(args.Key) actually returns a new CacheItem instance (NOT the one we set in Cache.Set). So the policy is lost.

    public override CacheItem GetCacheItem(string key, [Optional, DefaultParameterValue(null)] string regionName)
    
    {
    
    ...
    
    ...
    
    return new CacheItem(key, obj2);
    
    }

     

    Tuesday, February 22, 2011 7:05 PM

  • Yup that would be ideal. The problem is this line: 

     

     

    MyCacheItem item = cache.GetCacheItem(args.Key) as MyCacheItem;

    using reflector, I found out cache.GetCacheItem(args.Key) actually returns a new CacheItem instance (NOT the one we set in Cache.Set). So the policy is lost.

    public override CacheItem GetCacheItem(string key, [Optional, DefaultParameterValue(null)] string regionName)
    
    {
    
    ...
    
    ...
    
    return new CacheItem(key, obj2);
    
    }

     

    Looking at the code again in reflector, the policy object on the new custom cacheitem actually gets lost early in Set() function because it takes the key and value from the parameter and constructs a new MemoryCacheEntry object:

     

    [FROM REFLECTOR]

    public override void Set(CacheItem item, CacheItemPolicy policy)

    {

        if (item == null)

        {

            throw new ArgumentNullException("item");

        }

        this.Set(item.Key, item.Value, policy, null);

    }

     

     


    Tuesday, February 22, 2011 7:12 PM
  • I see. So create a custom type with a CacheItemPolicy property and put in CacheItem.Value ...?
    Dick Page
    Tuesday, February 22, 2011 8:10 PM
  • That would work. It's cleaner than maintaining a list of key policy pairs. I might go with that. Thanks.
    Wednesday, February 23, 2011 3:12 PM
  • Still inexcusable. The bit you added is clear as mud. There are no examples in most of the caching topics.
    Friday, June 08, 2012 3:58 PM