Rock the Cache Bar, Rock the Cache Bar
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 --------------------------------------- // // Output Debug String with time stamp. 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 objectMiscUtilities.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