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 is Object. 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 to GetObject (or a call to SaveObject). If no such object can be found and no other parameters are specified, the method will return null (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 is bool (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 the SaveObject 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 the GetObject method from the same thread using the name string. Unlike GetObject, the SaveObject method does not create the object for you, you have to create the object yourself before calling SaveObject.
  • 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 DUMMYCOMLib;
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.

using ThreadObjectManager;

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) HashTable 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

posted @ 2007-10-19 09:07  随风逝去(叶进)  阅读(557)  评论(1编辑  收藏  举报
Free Web Counter
Free Web Counter