Ok, I feel that I should have known this before, but until I tested this for myself today I wasn’t really sure and didn’t quite consider the implications.
Somebody on the Universal Thread asked about using the Cache object to store a DataSet and then reusing it. I replied not really thinking about this, saying that the DS gets serialized and then each new Cache request retrieves that instance from the serialized store.
Well, of course this is incorrect.
The Cache object lives inside the current AppDomain (ie. ASP.NET Application Scope) and it stores its entries in a Hashtable. When you store a reference object like a DataSet you are actually storing a live reference of that object, which means if you make a change to that object it’s reflected in all other clients that are trying to read that object or currently hold a reference.
The same is true with the Application object. With the Session object on the other hand it depends on how you store the object – InProc uses a live reference, but if you’re going to StateServer or SQLServer the reference is serialized:
Object |
Entry Storage |
Cache Object |
Live Reference |
Application Object |
Live Reference |
Session (InProc) |
Live Reference |
Session(StateServer/Sql Server) |
Serialized Reference |
What’s a bit surprising is that when I went looking for this information I couldn’t really dig up an entry that describes exactly what gets stored in the various State saving mechanisms. So I ran a very simple test using code like this:
// Put user code to initialize the page here
CacheItemTest T = this.Cache["Test"] as CacheItemTest;
if (T == null)
{
T = new CacheItemTest();
this.Cache["Test"] = T;
}
Response.Write("Cache: " + T.IntVal.ToString() + "<p>");
T.IntVal = T.IntVal +1;
where CacheItemTest is defined as:
public class CacheItemTest
{
public int IntVal = 0;
public string StrVal = "Rick";
public int Counter = 0;
}
If you run this you’ll see that the counter keeps going up on every request, which means that when we change the property we’re changing the live object that lives now both in our reference and a reference that the Cache object holds.
This has some fairly serious implications if you are updating data in this object. If you’re expecting that object to stay in a fixed state once pulled out of the Cache that will obviously not happen. This could also have implicit complications if you are dealing with an object stored that is not thread safe! For example, if you run some internal operation on an object that internally affects the state of the object by changing a field value this will affect the state of the object for the next caller or even one that already has a reference. For example, if there was some internal counter used to walk through a list and the counter was upped each time you went on to the next entry, and two requests did this at the same time the counter would get trashed and you’d get serious garbage. This is not a great example – after all that’s what iterators are for – but it gives you an idea.
It’s clear that Cache’d objects should be read only objects – unless you treat it like the multi-threaded and multi-referenced object that it actually is and handle synchronization properly. In this respect the Cache object behaves not much different than a pure static object reference. The advantage of Cache is that it handles object assignment synchronization so you’re guaranteed that an object gets created only once (which is a concern with statics in busy thread situations).
Session Object
It gets more interesting with the Session object. Consider this code adjusted for use with a Session object:
CacheItemTest TS = this.Session["Test"] as CacheItemTest;
if (TS == null)
{
TS = new CacheItemTest();
this.Session["Test"] = TS;
}
Response.Write("Session: " + TS.IntVal.ToString());
TS.IntVal = TS.IntVal +1;
With InProc Sessions the Cache object behaves as with the Cache object with an increasing counter: You’re pointing to a reference object that is returned to you from a Hashtable.
Now go into your web.config and change the operation to StateServer. The first thing that happens is that you get an error:
Unable to serialize the session state. Please note that non-serializable objects or MarshalByRef objects are not permitted when session state mode is 'StateServer' or 'SQLServer'.
What this means is that our type is not serializable so I have to add:
[Serializable]
to the type. Now it works…
I was actually surprised to see the behavior exactly the same as with InProc objects. Even though objects serialize ASP. Net tracks objects locally and writes out any changes out to the stateserver.
To see what’s happening I hooked up SQLServer Session state and fired up the SQL Monitor to watch what actually fires.
It appears that the Session object writes out the current Session, always, no matter whether you use it or not (assuming EnableSessionState is on on the page). There are no read operations once a session has been loaded once which means ASP.NET manages a copy in memory. But at the end of every hit the entire content of the session state gets dumped to the StateServer in what looks like binary serialization.
This means:
- You don’t have to write objects back to a session – it happens automatically
- Once you read an object it always gets written back whether you’ve touched it or not!
- You get all of the overhead of session serialization even if you don't use it or if you change a single value
I didn’t really get the second point until today – in fact in most of my apps I have something like this:
Session["Test"] = TS;
when the Session entry exists already and this has basically no effect at all.
It’s pretty important to understand these things when dealing with state stores. Obviously it would be a pretty big problem if you have huge amounts of state in your Session object as you’re sending all of this to the Session store EVERY time on every page hit!
For InProc objects there’s obviously a hell of a lot less overhead, since it doesn’t have any of this serialization and storage overhead. For Cache objects you have to be aware of the fact that you’re dealing essentially with an object accessed across multiple threads, so make sure that you are only using thread-safe objects and you don’t write data to these objects unless you write the proper synchronization code.
Other Posts you might also like