Thread variables and the .NET thread pool
Overview of the problem
ASP.NET web applications (including web services) use threads in a thread pool to process client requests. These threads are created by the .NET framework, they will be reused to process multiple requests. The number of threads in the pool will increase only when the number of simultaneous client requests increases. Typically, you have no control over which thread would be used to process a particular incoming request, your application could have processed thousands of requests from the users, but only one thread did all the work. The other extreme situation is that many requests came in at exactly the same moment so multiple threads have to be used to do the work.
Suppose a method in one of your classes is to be executed by threads in the thread pool, a good example is the page_load
method of your aspx
page class. How do you declare and use an object that is only visible
to the current thread? For example, you may want to use an old COM
object in this method. This COM object requires some heavy processing
at the time of creation and initialization. For performance reasons,
you probably don't want to create and initialize this COM object each
time your method is called.
If you declared the object as a global (static
or shared) variable, then all concurrently running threads would be
able to access it. Declaring the object as a non-static class variable
may not help because, for example, each time a new request reaches your
web application, a new instance of your aspx page class will be
used to process the request (even if the same thread in the thread pool
is doing the work), therefore you will have to create and initialize
your COM object for each user request.
There is nothing wrong with sharing an object between different threads if the object is thread-safe or you have added synchronization code so that only one thread can access it at a time (all the other threads will block until the owner thread releases the object). However, if your object is working on a lengthy database transaction, it may not be the best approach to require all the other threads to wait until the operation is completed.
In my recent article Using COM objects in multithreaded .NET applications, I presented a solution for this problem. Basically, I provide a utility class that helps you to create (and store) a COM object in the current thread and retrieve it at a later time. This is ideal for .NET applications that use a thread pool like ASP.NET. What I want to do in this article is generalize the idea to any object (it is not restricted to COM object any more).
The solution
My ObjMan
class is contained in the ThreadObjectManager.dll. It is modified from the code in my other article mentioned above. This class has three static
(shared) methods, GetObject
, SaveObject
, and RemoveObject
. Here is a description of these methods.
GetObject
: It takes three parameters, the return type isObject
. The first parameter is the name string of the object to be returned. This name string is supplied by the user to differentiate between different objects created within the same thread. The second and the third parameters are optional, they are the class name string and the assembly name string. When this method is called, it will retrieve the object with given name, if it is already created (or saved) within the same thread by a previous call toGetObject
(or a call toSaveObject
). If no such object can be found and no other parameters are specified, the method will returnnull
(Nothing ). If the class name and the assembly name parameters are specified, the method will use the parameters to load the corresponding assembly and to create an instance of the corresponding class, the newly created object will be saved with the given name string. The object with given name, if found or created, will be returned.SaveObject
: This method has two parameters and the return type isbool
(Boolean). The first parameter is the name string of the object to be saved. The second parameter is the object itself. If you create an object and call theSaveObject
method, passing a name string you choose and the object value, it will save your object for the current thread so that you can retrieve it later by calling theGetObject
method from the same thread using the name string. UnlikeGetObject
, theSaveObject
method does not create the object for you, you have to create the object yourself before callingSaveObject
.RemoveObject
: This method simply removes the object with given name from the current thread. It is not possible to remove an object created (or saved) from a different thread.
Please note that a method call such as GetObject("MyObjName")
returns different objects if executed from different threads, the
object name string only differentiates objects created (or saved)
within the same thread.
Here is a sample C# code of using the GetObject
method to create the COM object described in my other article. You need to register the DummyCom.dll and add it to your .NET project so that the .NET wrapper DLL will be generated.
using ThreadObjectManager;
Object obj = ObjMan.GetObject("MyDummyObj",
"DUMMYCOMLib.DummyObjClass", "Interop.DUMMYCOMLib");
String sOutput = ((DummyObjClass)obj).Test("This is a test");
// ObjMan.RemoveObject("MyDummyObj");
When the above code is executed, the COM object will be created only
once in each thread, unless you uncomment the line that calls RemoveObject
. For .NET internal objects, it is easier to combine the SaveObject
and the GetObject
methods as follows, since there is no need to load any assembly.
Object obj = GetObject("MyHashtable");
if(obj== null)
{
obj = new Hashtable();
ObjMan.SaveObject("MyHashtable", obj);
((Hashtable)obj).Add("MyString",
"This string is added at most once for each thread");
}
String sObjName = Guid.NewGuid().ToString();
Hashtable myHashtable = (Hashtable)obj;
myHashtable.Add(sObjName, "This string is added every time");
Again, the myHashtable
object will be created and saved only once for each thread that executes the above code.
The implementation
The implementation of the ObjMan
class is simple and straightforward. Internally, the ObjMan
class uses a global (static
) H
ashTable
to store data. Data items in this hashtable are hashtables themselves.
Each of these child hashtables corresponds to a thread, the key for the
parent hashtable is the thread name. Each thread accesses its own
hashtable using the thread name. If a thread has no name, a dynamically
generated GUID string will be assigned as its name.
When an object is created and saved, it is actually stored in the child hashtable of the current thread. The object name string will be the key for the child hashtable. When an object is retrieved, the code will first find the child hashtable for the current thread, then use the given object name string to find the item stored in the child hashtable.
From codeProject