MemoryMappedCache is a small project can be used to host a proactive loaded cache using a Windows Service. This cache uses Memory Mapped Files internally. Other applications on the same computer can then also use MemoryMappedCache to access information stored in this cache. This is done much quicker than using .NET Remoting. A significant amount of time was spent to ensure that this cache can be accessed from within ASP.NET if the cache is hosted by a Windows Service.
Before I describe how, let’s explain why: people often develop a three-tiered application containing a data-layer, a business-layer and one or more interfaces in the interface-layer:
Microsoft typically recommends to develop your Business Logic Components stateless to prevent any scalability-issues. This means that every time a request comes in, an instance of the Business Logic Component is created and the request is executed. The disadvantage of this way of working is that it makes caching in the Business Layer more difficult:
- If you've designed your business logic as a stateless Class Library than you can't cache any information in it because you can't maintain information between requests.
- You could cache all the necessary information in your different interfaces, but that prevents your Business Logic Components from using this cache and you have to design your cache all over for each interface.
- You could cache information in the database, but what's the point if the data is originally stored in a database? The purpose of the cache is to reduce the load on the database and to make information retrieval quicker.
A possible solution to this caching-problem is to develop a Windows Service that manages the cache. This service sits as an additional layer between the Database and the Business Logic Components. Every time the Business Logic Components need something, they could first check the cache managed by the Windows Service and, if the information is there, use it without accessing the database.
So we now need a fast way of accessing information hosted by a Windows Service using a Class Library and that's a problem because that involves interprocess-communication. The .NET Framework gives you a couple of solutions if you want to have two processes on the same computer to talk to each other in .NET: Web Services and .NET Remoting. The problem with both of these technologies is that they are very slow (at least compared to in-process communication)! Some tests that I did showed that accessing a Singleton-object hosted by a Windows Service using .NET Remoting isn't much faster than accessing the database directly (MSDN).
A much faster way of sharing information between two processes involves the usage of Memory Mapped Files. This is a feature provided by Windows and it can be easiest explained as an in-memory representation of a file's content that can be shared by several processes. The problem is that the .NET Framework 1.1 does not provide any native support for Memory Mapped Files. To my knowledge, this feature is not planned for the Visual Studio .NET 2005 either. Memory Mapped Files is the fastest way on Windows to share data between two processes on the same computer.
So, if you want to develop a .NET solution using Memory Mapped Files you have to use native Windows API-functions using P/Invoke. But since my C++ knowledge is rusty, I wanted to develop this using VB.NET or C# only. If you search the Internet, you'll notice that you won't find too many resources or sample applications showing you how to use Memory Mapped Files from within VB.NET or C#. I found two interesting resources however:
- One is an excellent article written by Natty Gur called DevGlobalCache – A way to Cache and Share data between processes. The sample code supplied by this article has one disadvantage though: if I hosted my cache in a Windows Service, I wasn't able to access it from within ASP.NET. I'll explain further down in this article why this doesn't work. Read Natty Gur's article to learn about Memory Mapped Files in detail. Some differences between DevGlobalCache and MemoryMappedCache are:
- MemoryMappedCache is written in VB.NET, DevGlobalCache is written in C#.
- MemoryMappedCache uses a simpler structure than DevGlobalCache for storing the data. It is because of this optimized for a cache using proactive loading.
- MemoryMappedCache uses its own Mutex-class. DevGlobalCache uses System.Threading.Mutex.
- Another good resource is the Caching Application Block developed by Microsoft. I was thrilled when I read the documentation of this Caching Application Block for the first time: it did exactly what I was looking for. However, I didn't use it for the following reasons:
- I found the documentation of this Application Block to be awful! I just didn't find the information that I needed to correctly configure the Caching Application Block. Also, the Quick Start Sample supplied with the Caching Application Block didn't help me any further: 99% of the sample-code demonstrates other Application Blocks and there less than 10 lines that are interesting for people that want to use the Caching Application Block.
- There were bugs in the VB.NET-version of the Caching Application Block and I found the whole thing to be too complex. I needed something much simpler.
- You also can't access a cache created in a Windows Service from within ASP.NET.
I actually started developing MemoryMappedCache based on the Microsoft.ApplicationBlocks.MemoryMappedFile-project and the Microsoft.ApplicationBlocks.Cache-project of the Caching Application Block. I stripped and changed these projects until they finally worked the way I wanted them to work and until they didn't contain any code anymore that I was never going to use. Significant pieces of the code had to be redesigned though to work in ASP.NET as described further down.
I've mentioned earlier that both of these solutions don't work if you want to access a cache created by a Windows Service from within ASP.NET. The problem is that both solutions create their Mutexes and the Memory Mapped Files using default security. More information about the meaning of default security and this problem can be found in the Mutex Madness-paragraph of Ask Dr. GUI #49: MSDN.
Please read MSDN for detailed information about default security, but I'll try to give you a simple explanation here. If you create a Mutex or a Memory Mapped File using Windows API, than you have three different options when it comes to security:
- You can use default security. The problem with a Memory Mapped File created with default security by a Windows Service (running using the Local System Account) is that an ASP.NET website (which is typically running using the IUSR_computer or the IWAM_computer-account) will not be granted access to the Memory Mapped File by Windows: an Access Denied-error will be raised.
- You can assign a NULL DACL. By assigning a NULL DACL you are granting everyone full access to the Memory Mapped File. This is the easiest thing to develop, and it is also the way used in the components explained below. A more secure approach (as described in the next bullet) should be used in a production environment though!
- You can explicitly assign proper rights. The Mutex Madness-paragraph mentioned above explains how to create a mutex (the same thing applies to a Memory Mapped File) using a more secure approach. I haven't been able to convert this C++ code to VB.NET yet, so if you feel like doing so, please mail me the code so that I can update this article.
For those of you who don't know what a mutex is, there's a good description in MSDN: "When two or more threads need to access a shared resource at the same time, the system needs a synchronization mechanism to ensure that only one thread at a time uses the resource. Mutex is a synchronization primitive that grants exclusive access to the shared resource to only one thread. If a thread acquires a mutex, the second thread that wants to acquire that mutex is suspended until the first thread releases the mutex."
As you may have learned so far, there are three obstacles to overcome if you want to use Memory Mapped Files in an architecture described above:
- You need to use P/Invoke to create and access any Memory Mapped Files. This makes the code more complex and more difficult to write.
- You can't create the Memory Mapped Files using default security because you're preventing access from ASP.NET.
- You can't create any mutexes using default security because you're also preventing access from ASP.NET. Fortunately, the .NET Framework has native support for mutexes using the System.Threading.Mutex-class. Unfortunately, that class appears to be creating its mutexes using default security so we can't use it. That's why I've developed my own Mutex-class in this project. This class creates mutexes using a NULL DACL. This is easy because it gives everyone full control but it's better to use the approach described in Mutex Madness of Ask Dr. GUI #49 in a production environment.
The MemoryMappedCache-project is currently designed with proactive content loading in mind. This means that the service is supposed to read all the information to be cached in memory when it starts and all the other applications (the clients so to speak) have read-only access to the cached information. If you want to, it should not be too difficult to change this project to a more reactive loading approach.
Using the code
When you open the MemoryMappedCache-solution you'll notice that it contains 4 small projects:
- Client is a sample Console Client-application that retrieves information from the cache created by the Server- or the Service-applications
- Server is a sample Console Server-application that stores information in the cache when it is started and then waits for user-input. While it is waiting, you can start the Client-application to retrieve information stored in the cache. Simply press Enter to quit the application.
- MemoryMappedCache is the Class Library that contains all the intelligence. This file contains the code necessary to manage the cache using Memory Mapped Files and it also contains code to create mutexes.
- The Service-application does exactly the same thing as the Server-application. The only difference is that this is a real Windows Service whereas Server is only a Console-application. Once you've compiled the service, you can install it using
installutil service.exe and start it using the Service Control Manager. It's called MemoryMapService.
Once you've been able to get the Client-application to work, you'll notice that it is very easy to develop an ASP.NET-solution that does the same thing.
Here's some sample code that shows you what happens when the server starts (I've replaced the Windows Service with a Console Application to make the code more readable):
Dim myCache As MemoryMappedCache.Cache
myCache = New MemoryMappedCache.Cache("MyCompany.MyApplication.Cache_")
Console.WriteLine("Server is ready. You may start" & _
" your client now. Press enter to stop the server.")
The first thing that happens is that an instance is made of the
MemoryMappedCache.Cache-object. The name specified in the constructor (
MyCompany.MyApplication.Cache_) can be any string and is used to uniquely identify this cache on this computer. The client-application should always use the same name as the server-application and two different server-applications should never use the same name. That's why I suggest using the MyCompany.MyApplication-convention. This should normally prevent you or anybody else from using the same name for another application.
After the server has created an instance of the
Cache-object, it should fill it with data. This is done using the
Assign-method. This method accepts a string as the key and any serializable object as its value. Here's what it will do:
- It will serialize the value to be stored in the cache. This is necessary because a Memory Mapped File is nothing more than a big chunk of memory which doesn't contain any structure.
- Calculate the number of bytes necessary to store this value. This will be the size of the serialized stream + 4 bytes. These 4 additional bytes are used as the first 4 bytes of the Memory Mapped File and contain the length of the data-stream that follows.
- Create a mutex that ensures exclusive access to this item
- Create a Memory Mapped File of the necessary size
- Write the length of the serialized value to the Memory Mapped File
- Write the serialized value to the Memory Mapped File
- Update the
Hashtable used inside the Cache-object which contains a list of all keys stored in the Cache and the corresponding file-handles to their Memory Mapped Files.
- Release the mutex
The MemoryMappedCache-project will create a separate Memory Mapped File for every item stored in the cache. Because of this, it's better to store a few large objects than a large number of small objects in the cache. If you have a lot of small values to store in the cache, create a separate class that contains all this information and then store this single class in the cache. This will work much faster than having dozens of small items. Every time an item is retrieved from the cache, the data has to be deserialized. This is also a rather costly operation which makes it another reason why it's best to have only a few large objects.
Once all the data has been loaded in the cache, the server is running. If you want to stop the server, make sure that you call the
Dispose-method. This method will close all handles opened by the Cache-object. You'll have memory-leaks if you don't do that!
We can now start our client-application and see if we can retrieve any of the information stored in the cache using the server. The sample Client Console-application is written in C# (it could be any other .NET compliant language):
static void Main(string args)
MemoryMappedCache.Cache myCache =
You can see that the cache can be used very easily. Simply create an instance of the
MemoryMappedCache.Cache-object using the same name as you used for your server and retrieve any values from the cache using the
Contents-function. Here's what this function will do:
- Create a mutex that ensures exclusive access to this item
- Open the Memory Mapped File containing the value
- Read the length of the data stored in the Memory Mapped File.
- Once the length has been determined, reading the remaining bytes of the Memory Mapped File. These bytes contain the serialized value.
- Deserialize the value
- Return the value
- Release the mutex
I've documented all important areas in the code. Please read the documentation in the source for more information on the inner-workings. The entire MemoryMappedCache-library has only 3 classes and contains less than 700 lines of code so it should be easy to read.
Points of Interest
All mutexes and Memory Mapped Files are created using a NULL DACL. Make sure to use a more secure design before using in a production environment.
- V1.1: A bug has been fixed that caused a memory leak. Thanks to Derek Wade for reporting the bug and the fix.