Click here to Skip to main content
Click here to Skip to main content
Technical Blog

Rock the Cache Bar, Rock the Cache Bar

, 2 Apr 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
It's possible you may develop dozens of web sites without ever doing any data caching. However, if you are trying to increase performance, data caching can be a key tool. The Asp.Net Cache object is remarkably easy to use, but it has many settings which makes it extremely powerful. The only way to

It's possible you may develop dozens of web sites without ever doing any data caching. However, if you are trying to increase performance, data caching can be a key tool. The Asp.Net Cache object is remarkably easy to use, but it has many settings which makes it extremely powerful.

The only way to learn and get a good understanding of the Cache object's features is to experiment. To keep my experiment from being too tedious, I use short times.

In the Web.Config file, I set the Session timeout to be 2 minutes:

<system.web>

  <sessionState timeout="2">  </sessionState>

</system.web>

Stepping through the code with the debugger will interfere with timings...so trace statements are used. In order to get timestamps in the trace statements, I used a function in a static utility class I have:  ODS stands for Output Debug String:

static public class MiscUtilities
{ 
    /// ---- ODS ---------------------------------------
    /// <summary>
    /// Output Debug String with time stamp.
    /// </summary>

    public static void ODS(string Msg)
    {
        String Out = String.Format("{0}  {1}",
            DateTime.Now.ToString("hh:mm:ss.ff"), Msg);
        System.Diagnostics.Debug.WriteLine(Out);
    }

I was interested in how the Cache interacted with normal page events. In the Global.asax file, I trace the events with my utility function:

<%@ Application Language="C#" %>

<script RunAt="server">

    void Application_Start(object sender, EventArgs e)
    {
        MiscUtilities.ODS("****ApplicationStart");
    }        
    void Application_BeginRequest(object sender, EventArgs e) 
    {
        MiscUtilities.ODS("Application_BeginRequest");
    }
    void Application_EndRequest(object sender, EventArgs e)
    {
        MiscUtilities.ODS("Application_EndRequest");
    }
    void Session_Start(object sender, EventArgs e)
    {
        MiscUtilities.ODS("Session_Start");
    }
    void Application_End(object sender, EventArgs e)
    {
        MiscUtilities.ODS("Application_End");
    }
    void Application_Error(object sender, EventArgs e)
    {
        MiscUtilities.ODS("Application_Error " + Server.GetLastError().Message);
    }
    void Session_End(object sender, EventArgs e)
    {
        MiscUtilities.ODS("Session_End");
    }
</script>

To explain the test we need to know what Sliding Expiration is. When you tell a Cache to use Sliding Expiration and give it a TimeSpan of 5 minutes, it may never time out. Every time the cache object is accessed, the timer is reset. This is great, if a cached object is used a lot, it will remain cached. Rarely used cached objects will be released.

If we use Absolute Expiration, then we use a DateTime to supply a specific time to release the cached object. If you know a database will be updated at 1:00 AM, you may want to have a cached item released and reset at 2:00 AM.

Side Note: You can also set a Cached object to expired when a file has changed. I didn't test this but I can see how it would be useful to allow nontechnical people to update the text content on a web page.

One of the most intriguing features of the Cache object is that you can have a function called when a Cache object expires. This is the feature I was most interested in.

Since the cache going to be set in two places, I created a function so all the settings would be in one place. For this test I used a string, but any serializable object can be placed in the Cache. I set the Cached object to expire 5 seconds in the future. A delegate is created for the callback function.

private void InsertToCache(String Key, String Item)
{
    Item = Item + "+"; // append a char to the cached object

    MiscUtilities.ODS("Cache Inserted: " + Key + " " + Item);

    CacheItemRemovedCallback RemoveCallBack = new CacheItemRemovedCallback(onCacheRemove);

    Cache.Insert(Key, 
        Item, 
        null,
        System.DateTime.Now.AddSeconds(5),
        System.Web.Caching.Cache.NoSlidingExpiration,
        System.Web.Caching.CacheItemPriority.Default,
        RemoveCallBack); 
}

I set the callback function to re-insert the item into the Cache:

// ---- onCacheRemove --------------------
//
// Fires when an item is removed from the cache

private void onCacheRemove(string Key, object Item, CacheItemRemovedReason Reason)
{
    MiscUtilities.ODS("onCacheRemove: " + Key + "  " + Reason.ToString());
 
    // re-insert the item back into the cache
    InsertToCache(Key, Item.ToString());
}

And finally, here is how I kicked it of:

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        InsertToCache("Key", "+");
    }
}

Here is the output:

04:42:30.93  ****ApplicationStart
04:42:30.94  Application_BeginRequest
04:42:32.40  Session_Start
04:42:32.46  Cache Inserted:  Key  ++
04:42:32.64  Application_EndRequest
04:42:32.67  Application_BeginRequest
04:42:32.68  Application_EndRequest
04:42:40.01  onCacheRemove: Key  Expired
04:42:40.01  Cache Inserted:  Key  +++
04:43:00.01  onCacheRemove: Key  Expired
04:43:00.01  Cache Inserted:  Key  ++++
04:43:20.01  onCacheRemove: Key  Expired
04:43:20.01  Cache Inserted:  Key  +++++
04:43:40.01  onCacheRemove: Key  Expired
04:43:40.01  Cache Inserted:  Key  ++++++
04:44:00.01  onCacheRemove: Key  Expired
04:44:00.01  Cache Inserted:  Key  +++++++
04:44:20.01  onCacheRemove: Key  Expired
04:44:20.01  Cache Inserted:  Key  ++++++++
04:44:40.01  onCacheRemove: Key  Expired
04:44:40.01  Cache Inserted:  Key  +++++++++
04:44:40.01  Session_End
04:45:00.01  onCacheRemove: Key  Expired
04:45:00.01  Cache Inserted:  Key  ++++++++++
04:45:20.01  onCacheRemove: Key  Expired
04:45:20.01  Cache Inserted:  Key  +++++++++++

Interesting Note 1:

Cached objects are independent of sessions. After the session that created the Cache object expired, the object lived on. Cached objects are shared across sessions and last for the duration of the application. If you have one hundred simultaneous users and they all need a static customer list from the database, you can save a boatload of database calls by caching the list.

Interesting Note 2:

I set the expiration date to be 5 seconds in the future but the Cached object expired every 20 seconds. What's that all about? I experimented with different time values:

Time to Expire (secs)

Time Actually Expired (secs)

3

20

5

20

20

40

30

40

50

60

70

80

Ahhh, there must be a thread that checks every 20 seconds for expired objects.

Interesting Note 3:

Two of the parameters of the Cache Insert function are

DateTime absoluteExpiration,
TimeSpan slidingExpiration

They are used like this:

Cache.Insert(Key,              
                            Item,
                            null,
                            System.DateTime.Now.AddSeconds(5),
                            System.Web.Caching.Cache.NoSlidingExpiration,
                            System.Web.Caching.CacheItemPriority.Default,
                            RemoveCallBack);

Or like this:

Cache.Insert(Key,
                            Item,
                            null,
                            System.Web.Caching.Cache.NoAbsoluteExpiration,
                            new TimeSpan(0, 0, 30),
                            System.Web.Caching.CacheItemPriority.Default,
                            RemoveCallBack);

The person who designed this used a trick. He created dummy variables with the correct type and gave them "Flag Names".

namespace System.Web.Caching
{    
    public sealed class Cache : IEnumerable  
    {        
        public static readonly DateTime NoAbsoluteExpiration;     
        public static readonly TimeSpan NoSlidingExpiration;

This allows the "flags" to be passed where a DateTime or TimeSpan is expected. While I admire the cleverness, I would have used an enum.

I hope someone finds this useful, or at least interesting.

Steve Wellens

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Steve Wellens
EndWell Software, Inc.
United States United States
I am an independent contractor/consultant working in the Twin Cities area in Minnesota. I work in .Net, Asp.Net, C#, C++, XML, SQL, Windows Forms, HTML, CSS, etc., etc., etc.

Comments and Discussions

 
GeneralThanks a lot Pinmembercneveu6-Apr-09 1:23 
GeneralRe: Thanks a lot PinmemberSteve Wellens6-Apr-09 2:59 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.141030.1 | Last Updated 2 Apr 2009
Article Copyright 2009 by Steve Wellens
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid