Simple Code Shell
Now that you’ve got a mapping for an HTTPHandler class, let’s take a closer look at a simple code shell for such a class.
Class WAC : Implements
Shared s_InitGUID As
Private m_InitGUID As
ProcessRequest(ByVal Context As
Property IsReusable() As
sets the shared InitGUID to a different
' value than that of the module level
' This will trigger an initialization on
' next object access, at which time a new
' InitGUID will be assigned. Following
' loaded instances will one by one
' themselves on their next access.
s_InitGUID = ""
method takes care of the initialization
' of objects which are shared across
' Since this is a pooled object, sharing
operations results in an increase in
' performance. However, care must be taken
' that transaction specific data from a
transaction does not impact on subsequent transactions.
If s_InitGUID = "" Then
If s_InitGUID = m_InitGUID Then
Do your initialization here.
' Then synchronize the private and shared markers.
m_InitGUID = s_InitGUID
It’s really quite simple. When IIS instantiates this class, or pulls it from the pool, it calls ProcessRequest, passing Context in as a parameter. From that point on you’ve got access to everything you need in order to process the request and send the response back to the browser.
The only other method which this interface provides is the IsReusable method which returns a Boolean. Simply return True and when control returns to IIS, it will release the object into a pool, rather than destroy it. The main thing to understand about pooling, is that the object will maintain its state between calls. This is a double edged sword. State which is generic, and relevant to all transactions across the board, should be maintained in order to eliminate the need to re-create it on successive transactions. State which is accumulated at the behest of a specific transaction must be either explicitly initialized at the beginning of ProcessRequest, or de-initialized at the end of ProcessRequest. Failing to do this might result in state from a previous transaction, having an adverse effect on a subsequent transaction.
In my application, the generic state which I maintain between transaction consists of several XML DOM’s created from external XML files. Consequently, the ability to cache this loaded information in a pooled object yields a significant performance improvement.
There is one drawback to this scheme however, since it is possible for the cached information in a pooled object to get out of sync with the information in the external files. To be sure, this is the exception rather than the rule since, by and large, the contents of these files don’t normally change on a day-to-day basis. However, it would be very convenient to have a mechanism whereby these pooled objects could be synchronized with the current external data without having to restart the web server.
I briefly considered using file modification dates in order to accomplish this, but quickly rejected this since I didn’t want to have to expend effort on each transaction for an occurrence which might happen only once a month. The method I chose leverages the new ability to define shared data variables in
The concept behind a shared variable is very similar to a variable in a VB6 global BAS module. That is, that all instances of the class loaded on the same thread will share that variable. Similarly, with
Visual Basic.NET, all instances of a class in the same AppDomain (roughly equivalent to a process) will share the same Shared variable. So this capability provides us with the basic approach. Using a shared variable as a toggle switch, it is possible to reach out and touch’ all class instances in order to inform them that some event has or should occur.
However, a single shared data point is insufficient for the operation we are contemplating. If the shared data point was the only value involved, then the first class instance to re-initialize itself would reset the toggle to indicate its own re-initialization has occurred, thus short-circuiting the mechanism for other class instances. The solution is to utilize two data points, a shared variable which is shared across all class instances, and a private variable which is specific to each class instance. In the example above, these two data points are s_InitGUID and m_InitGUID respectively.
The way the scheme works is that, basically, anytime a class initializes it copies the shared initialization GUID to it’s own private initialization GUID. As long as those two items are in sync, then the class does not re-initialize itself. This check is a simple, low-cost, string comparison which takes place at the beginning of zInit, which is the initialization routine called upon each entry into ProcessRequest.. Although zInit is invoked on every request, it rarely completes, since usually it finds the two variables are equivalent and exits immediately.
you’re after the ultimate tweak, and don’t mind cluttering up your
main line code a bit, then you can use the following branch to avoid going
into zInit in the first place, unless you actually need to.
If m_InitGUID = "" Or
m_InitGUID <> s_InitGUID Then
this case you won’t need the following conditional logic inside zInit.
If s_InitGUID = m_InitGUID Then
Here’s how the scheme works,
starting from application startup:
The first class to be instantiated finds s_InitGUID = ''. It assigns a baseline GUID to s_InitGUID which is then copied to its own
Subsequent classes which are instantiated find their internal s_InitGUID = '' which is different, after all, than the shared GUID. They therefore initialize themselves and assigned the shared initialization GUID to their own internal initialization
As classes are retrieved from the pool, they find that their internal GUID matches the shared GUID. They bypass initialization.
If the shared Refresh method is called, it will reset the shared GUID string to ''.
The next class to be instantiated or retrieved from the pool, finds s_InitGUID = ''. It assigns a new baseline GUID to s_InitGUID which is then copied to its own
Subsequent classes which are either instantiated, or retrieved form the pool, find their internal GUID’s to be different, than the shared GUID. They therefore initialize themselves and assigned the shared initialization GUID to their own internal initialization
Subsequent classes which are retrieved from the pool, and have already been re-initialized, find their internal GUID’s to be equal to the shared GUID. They therefore bypass initialization.
The following table summarizes this activity.
I'd like to emphasize that, in many ways, the methodology
of what I have presented is more significant than the actual
details are. I’d like to focus in on two specific points.
The first point is that multiple instances of a pooled class can maintain their own copies of application wide
. Some readers may object. So what? This functionality is already available in
several different manners. Even now, without pooled objects we keep our application wide data in centralized repository such as either the Application object, the Shared Property Manager (SPM) or the Global Information Table (GIT)’. These readers might also note that by using Shared class variables rather than Private class variables to maintain the data, only a single copy of the data needs to be maintained.
It is true that these are all viable alternatives. However the use of pooled objects and class private variables address certain circumstances for which the alternatives listed above are not viable. I’ll present a few examples in order to demonstrate that this method is simply one more tool in our development arsenal, to be used appropriately (as all tools should be used).
- Storing a VB6 object, or any thread-affinitive object, for global access, has always been problematic, since this, in effect, serializes every transaction which accesses the global object through a single thread. The technique presented above addresses this issue handily since each pooled transaction object maintains it’s own instance of the global access class(es) in its own class private
- Multiple clients accessing a common data repository involves problems of synchronization if one client tries to access the data while another client is updating the data. The technique presented above eliminates this issue entirely since each client maintains its own private copy of the data.
- The use of class private data is also viable for data which is likely to be application wide, but for which exceptions are bound to occur for specific transactions. A good example of this is a resource file. If we assume that 90% of the users are native language speakers, while 10% are foreigners, then we can use an optimistic’ algorithm to retain a resource file in preparation for the next transaction. If the next transaction uses the same language (which should occur about 80% of the time) then the language file need not be loaded. If the language of the subsequent transaction differs, then the new language file is loaded. Here we have a situation where we’d like to retain cached data across transactions, yet it’s obvious that this cached data can not be shared among all instances since different simultaneous transactions might be using different language files.